src/eric7/WebBrowser/WebBrowserWebAuthDialog.py

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

eric ide

mercurial