src/eric7/WebBrowser/WebBrowserWebAuthDialog.py

branch
eric7
changeset 10847
b3f04b83fc84
child 10851
5c47172bb240
equal deleted inserted replaced
10846:3db357d07c48 10847:b3f04b83fc84
1 # -*- coding: utf-8 -*-
2 # Copyright (c) 2024 Detlev Offenbach <detlev@die-offenbachs.de>
3 #
4
5
6 """
7 Module implementing a dialog to handle the various WebAuth requests.
8 """
9
10 from PyQt6.QtCore import Qt, pyqtSlot
11 from PyQt6.QtWebEngineCore import QWebEngineWebAuthUxRequest
12 from PyQt6.QtWidgets import (
13 QButtonGroup,
14 QDialog,
15 QDialogButtonBox,
16 QLineEdit,
17 QRadioButton,
18 QSizePolicy,
19 QVBoxLayout,
20 )
21
22 from eric7.EricGui import EricPixmapCache
23
24 from .Ui_WebBrowserWebAuthDialog import Ui_WebBrowserWebAuthDialog
25
26
27 class WebBrowserWebAuthDialog(QDialog, Ui_WebBrowserWebAuthDialog):
28 """
29 Class implementing a dialog to handle the various WebAuth requests.
30 """
31
32 def __init__(self, uxRequest, parent=None):
33 """
34 Constructor
35
36 @param uxRequest reference to the WebAuth request object
37 @type QWebEngineWebAuthUxRequest
38 @param parent reference to the parent widget (defaults to None)
39 @type QWidget (optional)
40 """
41 super().__init__(parent)
42 self.setupUi(self)
43
44 self.__uxRequest = uxRequest
45
46 self.selectAccountButtonGroup = QButtonGroup(self)
47 self.selectAccountButtonGroup.setExclusive(True)
48
49 self.selectAccountLayout = QVBoxLayout(self.selectAccountWidget)
50 self.selectAccountLayout.setAlignment(Qt.AlignmentFlag.AlignTop)
51
52 self.buttonBox.accepted.connect(self.__acceptRequest)
53 self.buttonBox.rejected.connect(self.__cancelRequest)
54 self.buttonBox.button(QDialogButtonBox.StandardButton.Retry).clicked.connect(
55 self.__retry
56 )
57
58 self.updateDialog()
59
60 self.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Expanding)
61
62 @pyqtSlot(str)
63 def on_pinEdit_textEdited(self, pin):
64 """
65 Private slot handling entering a PIN.
66
67 @param pin entered PIN
68 @type str
69 """
70 self.confirmPinErrorLabel.setVisible(
71 self.confirmPinEdit.isVisible() and pin != self.confirmPinEdit.text()
72 )
73 self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(
74 (self.confirmPinEdit.isVisible() and pin == self.confirmPinEdit.text())
75 or not self.confirmPinEdit.isVisible()
76 )
77
78 @pyqtSlot(bool)
79 def on_pinButton_toggled(self, checked):
80 """
81 Private slot to handle the toggling of the PIN visibility.
82
83 @param checked state of the PIN visibility button
84 @type bool
85 """
86 pinRequestInfo = self.__uxRequest.pinRequest()
87
88 if checked:
89 self.pinButton.setIcon(EricPixmapCache.getIcon("hidePassword"))
90 self.pinEdit.setEchoMode(QLineEdit.EchoMode.Normal)
91 else:
92 self.pinButton.setIcon(EricPixmapCache.getIcon("showPassword"))
93 self.pinEdit.setEchoMode(QLineEdit.EchoMode.Password)
94
95 if pinRequestInfo.reason != QWebEngineWebAuthUxRequest.PinEntryReason.Challenge:
96 self.confirmPinLabel.setVisible(not checked)
97 self.confirmPinEdit.setVisible(not checked)
98 self.on_pinEdit_textEdited(self.pinEdit.text())
99
100 @pyqtSlot(str)
101 def on_confirmPinEdit_textEdited(self, pin):
102 """
103 Private slot handling entering of a confirmation PIN.
104
105 @param pin entered confirmation PIN
106 @type str
107 """
108 self.confirmPinErrorLabel.setVisible(pin != self.pinEdit.text())
109 self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(
110 pin == self.confirmPinEdit.text()
111 )
112
113 @pyqtSlot()
114 def __acceptRequest(self):
115 """
116 Private slot to accept the WebAuth request.
117 """
118 # TODO: not implemented yet
119 requestState = self.__uxRequest.state()
120 if requestState == QWebEngineWebAuthUxRequest.WebAuthUxState.SelectAccount:
121 checkedButton = self.selectAccountButtonGroup.checkedButton()
122 if checkedButton:
123 self.__uxRequest.setSelectedAccount(checkedButton.text())
124 elif requestState == QWebEngineWebAuthUxRequest.WebAuthUxState.CollectPin:
125 self.__uxRequest.setPin(self.pinEdit.text())
126
127 @pyqtSlot()
128 def __cancelRequest(self):
129 """
130 Private slot to cancel the WebAuth request.
131 """
132 # TODO: not implemented yet
133 self.__uxRequest.cancel()
134
135 @pyqtSlot()
136 def __retry(self):
137 """
138 Private slot to retry the WebAuth request.
139 """
140 # TODO: not implemented yet
141 self.__uxRequest.retry()
142
143 @pyqtSlot()
144 def updateDialog(self):
145 """
146 Public slot to update the dialog depending on the current WebAuth request state.
147 """
148 requestState = self.__uxRequest.state()
149 if requestState == QWebEngineWebAuthUxRequest.WebAuthUxState.SelectAccount:
150 self.__setupSelectAccountUi()
151 elif requestState == QWebEngineWebAuthUxRequest.WebAuthUxState.CollectPin:
152 self.__setupCollectPinUi()
153 elif (
154 requestState
155 == QWebEngineWebAuthUxRequest.WebAuthUxState.FinishTokenCollection
156 ):
157 self.__setupFinishCollectTokenUi()
158 elif requestState == QWebEngineWebAuthUxRequest.WebAuthUxState.RequestFailed:
159 self.__setupErrorUi
160
161 self.adjustSize()
162
163 def __setupSelectAccountUi(self):
164 """
165 Private method to configure the 'Select Account' UI.
166 """
167 # TODO: not implemented yet
168 self.__clearSelectAccountButtons()
169
170 self.headerLabel.setText(self.tr("<b>Choose Passkey</b>"))
171 self.descriptionLabel.setText(
172 self.tr("Which passkey do you want to use for {0}?").format(
173 self.__uxRequest.relyingPartyId()
174 )
175 )
176 self.pinGroupBox.setVisible(False)
177
178 self.selectAccountArea.setVisible(True)
179 self.selectAccountWidget.resize(self.width(), self.height())
180 userNames = self.__uxRequest.userNames()
181 for name in sorted(userNames):
182 button = QRadioButton(name)
183 self.selectAccountLayout.addWidget(button)
184 self.selectAccountButtonGroup.addButton(button)
185 if len(userNames) == 1:
186 # nothing to select from, select the one and only button
187 self.selectAccountButtonGroup.buttons()[0].setChecked(True)
188
189 self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setText(self.tr("Ok"))
190 self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setVisible(True)
191 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setVisible(True)
192 self.buttonBox.button(QDialogButtonBox.StandardButton.Retry).setVisible(False)
193
194 def __setupCollectPinUi(self):
195 """
196 Private method to configure the 'Collect PIN' UI.
197 """
198 # TODO: not implemented yet
199 self.__clearSelectAccountButtons()
200
201 self.selectAccountArea.setVisible(False)
202
203 self.pinGroupBox.setVisible(False)
204 self.confirmPinLabel.setVisible(False)
205 self.confirmPinEdit.setVisible(False)
206 self.confirmPinErrorLabel.setVisible(False)
207
208 self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setText(
209 self.tr("Next")
210 )
211 self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setVisible(True)
212 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setVisible(True)
213 self.buttonBox.button(QDialogButtonBox.StandardButton.Retry).setVisible(False)
214
215 pinRequestInfo = self.__uxRequest.pinRequest()
216 if pinRequestInfo.reason == QWebEngineWebAuthUxRequest.PinEntryReason.Challenge:
217 self.headerLabel.setText(self.tr("<b>PIN Required</b>"))
218 self.descriptionLabel.setText(
219 self.tr("Enter the PIN for your security key.")
220 )
221 else:
222 if pinRequestInfo.reason == QWebEngineWebAuthUxRequest.PinEntryReason.Set:
223 self.headerLabel.setText(self.tr("<b>New PIN Required</b>"))
224 self.descriptionLabel.setText(
225 self.tr("Set new PIN for your security key.")
226 )
227 else:
228 self.headerLabel.setText(self.tr("<b>PIN Change Required</b>"))
229 self.descriptionLabel.setText(
230 self.tr("Change the PIN for your security key.")
231 )
232 self.confirmPinLabel.setVisible(True)
233 self.confirmPinEdit.setVisible(True)
234
235 errorDetails = ""
236 if (
237 pinRequestInfo.error
238 == QWebEngineWebAuthUxRequest.PinEntryError.InternalUvLocked
239 ):
240 errorDetails = self.tr("Internal User Verification Locked!")
241 elif pinRequestInfo.error == QWebEngineWebAuthUxRequest.PinEntryError.WrongPin:
242 errorDetails = self.tr("Wrong PIN!")
243 elif pinRequestInfo.error == QWebEngineWebAuthUxRequest.PinEntryError.TooShort:
244 errorDetails = self.tr("PIN Too Short!")
245 elif (
246 pinRequestInfo.error
247 == QWebEngineWebAuthUxRequest.PinEntryError.InvalidCharacters
248 ):
249 errorDetails = self.tr("PIN Contains Invalid Characters!")
250 elif (
251 pinRequestInfo.error
252 == QWebEngineWebAuthUxRequest.PinEntryError.SameAsCurrentPin
253 ):
254 errorDetails = self.tr("New PIN is same as current PIN!")
255 if errorDetails:
256 errorDetails = self.tr(
257 "{0} %n attempt(s) remaining.", "", pinRequestInfo.remainingAttempts
258 ).format(errorDetails)
259 self.pinErrorLabel.setText(errorDetails)
260
261 def __setupFinishCollectTokenUi(self):
262 """
263 Private method to configure the 'Finish Collect Token' UI.
264 """
265 # TODO: not implemented yet
266 self.__clearSelectAccountButtons()
267
268 self.headerLabel.setText(self.tr("<b>Use your security key with {0}</b>"))
269 self.descriptionLabel.setText(
270 self.tr("Touch your security key to complete the request.")
271 )
272 self.pinGroupBox.setVisible(False)
273
274 self.selectAccountArea.setVisible(False)
275
276 self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setVisible(False)
277 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setVisible(True)
278 self.buttonBox.button(QDialogButtonBox.StandardButton.Retry).setVisible(False)
279
280 def __setupErrorUi(self):
281 """
282 Private method to configure the 'Error' UI.
283 """
284 # TODO: not implemented yet
285 self.__clearSelectAccountButtons()
286
287 errorMsg = ""
288 retryVisible = False
289
290 requestFailureReason = self.__uxRequest.requestFailureReason()
291 if (
292 requestFailureReason
293 == QWebEngineWebAuthUxRequest.RequestFailureReason.Timeout
294 ):
295 errorMsg = self.tr("Request Timeout")
296 elif (
297 requestFailureReason
298 == QWebEngineWebAuthUxRequest.RequestFailureReason.KeyNotRegistered
299 ):
300 errorMsg = self.tr("Security key is not registered.")
301 elif (
302 requestFailureReason
303 == QWebEngineWebAuthUxRequest.RequestFailureReason.KeyAlreadyRegistered
304 ):
305 errorMsg = self.tr(
306 "You already registered this security key. Try again with another"
307 " security key."
308 )
309 retryVisible = True
310 elif (
311 requestFailureReason
312 == QWebEngineWebAuthUxRequest.RequestFailureReason.SoftPinBlock
313 ):
314 errorMsg = self.tr(
315 "The security key is locked because the wrong PIN was entered too"
316 " many times. To unlock it, remove and reinsert it."
317 )
318 retryVisible = True
319 elif (
320 requestFailureReason
321 == QWebEngineWebAuthUxRequest.RequestFailureReason.HardPinBlock
322 ):
323 errorMsg = self.tr(
324 "The security key is locked because the wrong PIN was entered too"
325 " many times. You will need to reset the security key."
326 )
327 elif (
328 requestFailureReason
329 == QWebEngineWebAuthUxRequest.RequestFailureReason.AuthenticatorRemovedDuringPinEntry # noqa: E501
330 ):
331 errorMsg = self.tr(
332 "Security key removed during verification. Please reinsert and try"
333 " again."
334 )
335 elif (
336 requestFailureReason
337 == QWebEngineWebAuthUxRequest.RequestFailureReason.AuthenticatorMissingResidentKeys # noqa: E501
338 ):
339 errorMsg = self.tr("Security key doesn't have resident key support.")
340 elif (
341 requestFailureReason
342 == QWebEngineWebAuthUxRequest.RequestFailureReason.AuthenticatorMissingUserVerification # noqa: E501
343 ):
344 errorMsg = self.tr("Security key is missing user verification.")
345 elif (
346 requestFailureReason
347 == QWebEngineWebAuthUxRequest.RequestFailureReason.AuthenticatorMissingLargeBlob # noqa: E501
348 ):
349 errorMsg = self.tr("Security key is missing Large Blob support.")
350 elif (
351 requestFailureReason
352 == QWebEngineWebAuthUxRequest.RequestFailureReason.NoCommonAlgorithms
353 ):
354 errorMsg = self.tr("Security key does not provide a common algorithm.")
355 elif (
356 requestFailureReason
357 == QWebEngineWebAuthUxRequest.RequestFailureReason.StorageFull
358 ):
359 errorMsg = self.tr("No storage space left on the security key.")
360 elif (
361 requestFailureReason
362 == QWebEngineWebAuthUxRequest.RequestFailureReason.UserConsentDenied
363 ):
364 errorMsg = self.tr("User consent denied.")
365 elif (
366 requestFailureReason
367 == QWebEngineWebAuthUxRequest.RequestFailureReason.WinUserCancelled
368 ):
369 errorMsg = self.tr("User cancelled the WebAuth request.")
370
371 self.headerLabel.setText(self.tr("<b>Something went wrong</b>"))
372 self.descriptionLabel.setText(errorMsg)
373 self.descriptionLabel.adjustSize()
374
375 self.pinGroupBox.setVisible(False)
376 self.selectAccountArea.setVisible(False)
377
378 self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setVisible(False)
379 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setVisible(True)
380 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setText(
381 self.tr("Close")
382 )
383 self.buttonBox.button(QDialogButtonBox.StandardButton.Retry).setVisible(
384 retryVisible
385 )
386 if retryVisible:
387 self.buttonBox.button(QDialogButtonBox.StandardButton.Retry).setFocus()
388
389 def __clearSelectAccountButtons(self):
390 """
391 Private method to remove the account selection buttons.
392 """
393 for button in self.selectAccountButtonGroup.buttons():
394 self.selectAccountLayout.removeWidget(button)
395 self.selectAccountButtonGroup.removeButton(button)

eric ide

mercurial