summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAnu Aliyas <anu.aliyas@qt.io>2023-05-12 13:32:12 +0200
committerAnu Aliyas <anu.aliyas@qt.io>2023-07-12 17:20:57 +0200
commit605b0b3dcce24ff82c1e7a1ab3db7dace9668b81 (patch)
treee688549a6ef361f243ec82a406abb875ae0ee371
parenta3452104907874f4a3ffee83ec99c639004405e6 (diff)
Support FIDO2 user verification
- Implemented AuthenticatorRequestClientDelegateQt support to handle authenticator requests. - Added FIDO user verification and resident credential support Fixes: QTBUG-90938 Fixes: QTBUG-90941 Change-Id: I6367791e1e9e8aaac27c376408377f838832f426 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Michael BrĂ¼ning <michael.bruning@qt.io>
-rw-r--r--examples/webenginequick/quicknanobrowser/BrowserWindow.qml8
-rw-r--r--examples/webenginequick/quicknanobrowser/CMakeLists.txt1
-rw-r--r--examples/webenginequick/quicknanobrowser/WebAuthDialog.qml287
-rw-r--r--examples/webenginequick/quicknanobrowser/doc/src/quicknanobrowser.qdoc24
-rw-r--r--examples/webenginequick/quicknanobrowser/resources.qrc1
-rw-r--r--examples/webenginewidgets/simplebrowser/CMakeLists.txt1
-rw-r--r--examples/webenginewidgets/simplebrowser/doc/src/simplebrowser.qdoc24
-rw-r--r--examples/webenginewidgets/simplebrowser/simplebrowser.pro9
-rw-r--r--examples/webenginewidgets/simplebrowser/webauthdialog.cpp294
-rw-r--r--examples/webenginewidgets/simplebrowser/webauthdialog.h41
-rw-r--r--examples/webenginewidgets/simplebrowser/webauthdialog.ui151
-rw-r--r--examples/webenginewidgets/simplebrowser/webview.cpp30
-rw-r--r--examples/webenginewidgets/simplebrowser/webview.h5
-rw-r--r--src/core/CMakeLists.txt2
-rw-r--r--src/core/api/CMakeLists.txt1
-rw-r--r--src/core/api/qwebenginepage.cpp8
-rw-r--r--src/core/api/qwebenginepage.h3
-rw-r--r--src/core/api/qwebenginepage_p.h1
-rw-r--r--src/core/api/qwebenginewebauthuxrequest.cpp431
-rw-r--r--src/core/api/qwebenginewebauthuxrequest.h145
-rw-r--r--src/core/api/qwebenginewebauthuxrequest_p.h43
-rw-r--r--src/core/authenticator_request_client_delegate_qt.cpp246
-rw-r--r--src/core/authenticator_request_client_delegate_qt.h96
-rw-r--r--src/core/authenticator_request_dialog_controller.cpp301
-rw-r--r--src/core/authenticator_request_dialog_controller.h56
-rw-r--r--src/core/authenticator_request_dialog_controller_p.h79
-rw-r--r--src/core/content_browser_client_qt.cpp16
-rw-r--r--src/core/content_browser_client_qt.h6
-rw-r--r--src/core/doc/src/qwebenginepage_lgpl.qdoc12
-rw-r--r--src/core/web_contents_adapter_client.h2
-rw-r--r--src/webenginequick/api/qquickwebengineforeigntypes_p.h8
-rw-r--r--src/webenginequick/api/qquickwebengineview.cpp7
-rw-r--r--src/webenginequick/api/qquickwebengineview_p.h2
-rw-r--r--src/webenginequick/api/qquickwebengineview_p_p.h1
-rw-r--r--src/webenginequick/doc/src/webengineview_lgpl.qdoc12
-rw-r--r--tests/auto/quick/publicapi/tst_publicapi.cpp47
36 files changed, 2398 insertions, 3 deletions
diff --git a/examples/webenginequick/quicknanobrowser/BrowserWindow.qml b/examples/webenginequick/quicknanobrowser/BrowserWindow.qml
index d5ae19f54..bfbe13f6d 100644
--- a/examples/webenginequick/quicknanobrowser/BrowserWindow.qml
+++ b/examples/webenginequick/quicknanobrowser/BrowserWindow.qml
@@ -607,6 +607,9 @@ ApplicationWindow {
featurePermissionDialog.feature = feature;
featurePermissionDialog.visible = true;
}
+ onWebAuthUXRequested: function(request) {
+ webAuthDialog.init(request);
+ }
Timer {
id: reloadTimer
@@ -759,6 +762,11 @@ ApplicationWindow {
anchors.fill: parent
}
+ WebAuthDialog {
+ id: webAuthDialog
+ visible: false
+ }
+
function onDownloadRequested(download) {
downloadView.visible = true;
downloadView.append(download);
diff --git a/examples/webenginequick/quicknanobrowser/CMakeLists.txt b/examples/webenginequick/quicknanobrowser/CMakeLists.txt
index c1f0080bb..8a1a9b706 100644
--- a/examples/webenginequick/quicknanobrowser/CMakeLists.txt
+++ b/examples/webenginequick/quicknanobrowser/CMakeLists.txt
@@ -53,6 +53,7 @@ set(resources_resource_files
"DownloadView.qml"
"FindBar.qml"
"FullScreenNotification.qml"
+ "WebAuthDialog.qml"
)
qt_add_resources(quicknanobrowser "resources"
diff --git a/examples/webenginequick/quicknanobrowser/WebAuthDialog.qml b/examples/webenginequick/quicknanobrowser/WebAuthDialog.qml
new file mode 100644
index 000000000..4af401237
--- /dev/null
+++ b/examples/webenginequick/quicknanobrowser/WebAuthDialog.qml
@@ -0,0 +1,287 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtWebEngine
+
+Dialog {
+ id: webAuthDialog
+ anchors.centerIn: parent
+ width: Math.min(browserWindow.width, browserWindow.height) / 3 * 2
+ contentWidth: verticalLayout.width +10;
+ contentHeight: verticalLayout.height +10;
+ standardButtons: Dialog.Cancel | Dialog.Apply
+ title: "WebAuth Request"
+
+ property var selectAccount;
+ property var authrequest: null;
+
+ Connections {
+ id: webauthConnection
+ ignoreUnknownSignals: true
+ function onStateChanged(state) {
+ webAuthDialog.setupUI(state);
+ }
+ }
+
+ onApplied: {
+ switch (webAuthDialog.authrequest.state) {
+ case WebEngineWebAuthUXRequest.CollectPIN:
+ webAuthDialog.authrequest.setPin(pinEdit.text);
+ break;
+ case WebEngineWebAuthUXRequest.SelectAccount:
+ webAuthDialog.authrequest.setSelectedAccount(webAuthDialog.selectAccount);
+ break;
+ default:
+ break;
+ }
+ }
+
+ onRejected: {
+ webAuthDialog.authrequest.cancel();
+ }
+
+ function init(request) {
+ pinLabel.visible = false;
+ pinEdit.visible = false;
+ confirmPinLabel.visible = false;
+ confirmPinEdit.visible = false;
+ selectAccountModel.clear();
+ webAuthDialog.authrequest = request;
+ webauthConnection.target = request;
+ setupUI(webAuthDialog.authrequest.state)
+ webAuthDialog.visible = true;
+ pinEntryError.visible = false;
+ }
+
+ function setupUI(state) {
+ switch (state) {
+ case WebEngineWebAuthUXRequest.SelectAccount:
+ setupSelectAccountUI();
+ break;
+ case WebEngineWebAuthUXRequest.CollectPIN:
+ setupCollectPIN();
+ break;
+ case WebEngineWebAuthUXRequest.FinishTokenCollection:
+ setupFinishCollectToken();
+ break;
+ case WebEngineWebAuthUXRequest.RequestFailed:
+ setupErrorUI();
+ break;
+ case WebEngineWebAuthUXRequest.Completed:
+ webAuthDialog.close();
+ break;
+ }
+ }
+
+ ButtonGroup {
+ id : selectAccount;
+ exclusive: true;
+ }
+
+ ListModel {
+ id: selectAccountModel
+
+ }
+ contentItem: Item {
+ ColumnLayout {
+ id : verticalLayout
+ spacing : 10
+
+ Label {
+ id: heading
+ text: "";
+ }
+
+ Label {
+ id: description
+ text: "";
+ }
+
+ Row {
+ spacing : 10
+ Label {
+ id: pinLabel
+ text: "PIN";
+ }
+ TextInput {
+ id: pinEdit
+ text: "EnterPin"
+ enabled: true
+ focus: true
+ color: "white"
+ layer.sourceRect: {
+ Rectangle: {
+ width: 20
+ height: 20
+ color: "#00B000"
+ }
+ }
+ }
+ }
+
+ Row {
+ spacing : 10
+ Label {
+ id: confirmPinLabel
+ text: "Confirm PIN";
+ }
+ TextEdit {
+ id: confirmPinEdit
+ text: ""
+ }
+ }
+
+ Label {
+ id: pinEntryError
+ text: "";
+ }
+
+ Repeater {
+ id : selectAccountRepeater
+ model: selectAccountModel
+ Column {
+ spacing : 5
+ RadioButton {
+ text: modelData
+ ButtonGroup.group : selectAccount;
+ onClicked: function(){
+ webAuthDialog.selectAccount = text;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ function setupSelectAccountUI() {
+ webAuthDialog.selectAccount = "";
+ heading.text = "Choose a passkey";
+ description.text = "Which passkey do you want to use for " + webAuthDialog.authrequest.relyingPartyId;
+
+ selectAccountModel.clear();
+ var userNames = webAuthDialog.authrequest.userNames;
+ for (var i = 0; i < userNames.length; i++) {
+ selectAccountModel.append( {"name" : userNames[i]});
+ }
+ pinLabel.visible = false;
+ pinEdit.visible = false;
+ confirmPinLabel.visible = false;
+ confirmPinEdit.visible = false;
+ pinEntryError.visible = false;
+ standardButton(Dialog.Apply).visible = true;
+ standardButton(Dialog.Cancel).visible = true;
+ standardButton(Dialog.Cancel).text ="Cancel"
+ }
+
+ function setupCollectPIN() {
+ var requestInfo = webAuthDialog.authrequest.pinRequest;
+
+ pinEdit.clear();
+
+ if (requestInfo.reason === WebEngineWebAuthUXRequest.Challenge) {
+ heading.text = "PIN required";
+ description.text = "Enter the PIN for your security key";
+ pinLabel.visible = true;
+ pinEdit.visible = true;
+ confirmPinLabel.visible = false;
+ confirmPinEdit.visible = false;
+ } else if (reason === WebEngineWebAuthUXRequest.Set) {
+ heading.text = "Set PIN ";
+ description.text = "Set new PIN for your security key";
+ pinLabel.visible = true;
+ pinEdit.visible = true;
+ confirmPinLabel.visible = true;
+ confirmPinEdit.visible = true;
+ }
+ pinEntryError.text = getPINErrorDetails() + " " + requestInfo.remainingAttempts + " attempts reamining";
+ pinEntryError.visible = true;
+ selectAccountModel.clear();
+ standardButton(Dialog.Cancel).visible = true;
+ standardButton(Dialog.Cancel).text ="Cancel"
+ standardButton(Dialog.Apply).visible = true;
+ }
+
+ function getPINErrorDetails() {
+ var requestInfo = webAuthDialog.authrequest.pinRequest;
+ switch (requestInfo.error) {
+ case WebEngineWebAuthUXRequest.NoError:
+ return "";
+ case WebEngineWebAuthUXRequest.TooShort:
+ return "Too short";
+ case WebEngineWebAuthUXRequest.InternalUvLocked:
+ return "Internal Uv Locked";
+ case WebEngineWebAuthUXRequest.WrongPIN:
+ return "Wrong PIN";
+ case WebEngineWebAuthUXRequest.InvalidCharacters:
+ return "Invalid Characters";
+ case WebEngineWebAuthUXRequest.SameAsCurrentPIN:
+ return "Same as current PIN";
+ }
+ }
+
+ function getRequestFailureResaon() {
+ var requestFailureReason = webAuthDialog.authrequest.requestFailureReason;
+ switch (requestFailureReason) {
+ case WebEngineWebAuthUXRequest.Timeout:
+ return " Request Timeout";
+ case WebEngineWebAuthUXRequest.KeyNotRegistered:
+ return "Key not registered";
+ case WebEngineWebAuthUXRequest.KeyAlreadyRegistered:
+ return "You already registered this device. You don't have to register it again
+ Try agin with different key or device";
+ case WebEngineWebAuthUXRequest.SoftPINBlock:
+ return "The security key is locked because the wrong PIN was entered too many times.
+ To unlock it, remove and reinsert it.";
+ case WebEngineWebAuthUXRequest.HardPINBlock:
+ return "The security key is locked because the wrong PIN was entered too many times.
+ You'll need to reset the security key.";
+ case WebEngineWebAuthUXRequest.AuthenticatorRemovedDuringPINEntry:
+ return "Authenticator removed during verification. Please reinsert and try again";
+ case WebEngineWebAuthUXRequest.AuthenticatorMissingResidentKeys:
+ return "Authenticator doesn't have resident key support";
+ case WebEngineWebAuthUXRequest.AuthenticatorMissingUserVerification:
+ return "Authenticator missing user verification";
+ case WebEngineWebAuthUXRequest.AuthenticatorMissingLargeBlob:
+ return "Authenticator missing Large Blob support";
+ case WebEngineWebAuthUXRequest.NoCommonAlgorithms:
+ return "No common Algorithms";
+ case WebEngineWebAuthUXRequest.StorageFull:
+ return "Storage full";
+ case WebEngineWebAuthUXRequest.UserConsentDenied:
+ return "User consent denied";
+ case WebEngineWebAuthUXRequest.WinUserCancelled:
+ return "User cancelled request";
+ }
+ }
+
+ function setupFinishCollectToken() {
+ heading.text = "Use your security key with " + webAuthDialog.authrequest.relyingPartyId;
+ description.text = "Touch your security key again to complete the request.";
+ pinLabel.visible = false;
+ pinEdit.visible = false;
+ confirmPinLabel.visible = false;
+ confirmPinEdit.visible = false;
+ selectAccountModel.clear();
+ pinEntryError.visible = false;
+ standardButton(Dialog.Apply).visible = false;
+ standardButton(Dialog.Cancel).visible = true;
+ standardButton(Dialog.Cancel).text ="Cancel"
+ }
+
+ function setupErrorUI() {
+ heading.text = "Something went wrong";
+ description.text = getRequestFailureResaon();
+ pinLabel.visible = false;
+ pinEdit.visible = false;
+ confirmPinLabel.visible = false;
+ confirmPinEdit.visible = false;
+ selectAccountModel.clear();
+ pinEntryError.visible = false;
+ standardButton(Dialog.Apply).visible = false;
+ standardButton(Dialog.Cancel).visible = true;
+ standardButton(Dialog.Cancel).text ="Close"
+ }
+}
diff --git a/examples/webenginequick/quicknanobrowser/doc/src/quicknanobrowser.qdoc b/examples/webenginequick/quicknanobrowser/doc/src/quicknanobrowser.qdoc
index e99642661..21313c1a4 100644
--- a/examples/webenginequick/quicknanobrowser/doc/src/quicknanobrowser.qdoc
+++ b/examples/webenginequick/quicknanobrowser/doc/src/quicknanobrowser.qdoc
@@ -143,6 +143,30 @@
\skipto Escape
\printuntil /^\ {4}\}/
+ \section1 Handling WebAuth/FIDO UX Requests
+
+ We use the \c onWebAuthUXRequested() signal handler to handle requests for
+ WebAuth/FIDO UX. The \c request parameter is an instance of WebEngineWebAuthUXRequest
+ which contains UX request details and APIs required to process the request.
+ We use it to construct WebAuthUX dialog and initiates the UX request flow.
+
+ \quotefromfile webenginequick/quicknanobrowser/BrowserWindow.qml
+ \skipto onWebAuthUXRequested
+ \printuntil }
+
+ The \l WebEngineWebAuthUXRequest object periodically emits the \l
+ {WebEngineWebAuthUXRequest::}{stateChanged} signal to notify potential
+ observers of the current WebAuth UX states. The observers update the WebAuth
+ dialog accordingly. We use onStateChanged() signal handler to handle
+ state change requests. See \c WebAuthDialog.qml for an example
+ of how these signals can be handled.
+
+ \quotefromfile webenginequick/quicknanobrowser/WebAuthDialog.qml
+ \skipto Connections
+ \printuntil }
+ \skipto function init(request)
+ \printuntil }
+
\section1 Files and Attributions
The example uses icons from the Tango Icon Library:
diff --git a/examples/webenginequick/quicknanobrowser/resources.qrc b/examples/webenginequick/quicknanobrowser/resources.qrc
index 9d1f927d3..0a0b42bbb 100644
--- a/examples/webenginequick/quicknanobrowser/resources.qrc
+++ b/examples/webenginequick/quicknanobrowser/resources.qrc
@@ -6,6 +6,7 @@
<file>DownloadView.qml</file>
<file>FindBar.qml</file>
<file>FullScreenNotification.qml</file>
+ <file>WebAuthDialog.qml</file>
</qresource>
<qresource prefix="/icons">
<file alias="go-next.png">icons/3rdparty/go-next.png</file>
diff --git a/examples/webenginewidgets/simplebrowser/CMakeLists.txt b/examples/webenginewidgets/simplebrowser/CMakeLists.txt
index cb0769e77..a572e53f5 100644
--- a/examples/webenginewidgets/simplebrowser/CMakeLists.txt
+++ b/examples/webenginewidgets/simplebrowser/CMakeLists.txt
@@ -27,6 +27,7 @@ qt_add_executable(simplebrowser
webpage.cpp webpage.h
webpopupwindow.cpp webpopupwindow.h
webview.cpp webview.h
+ webauthdialog.cpp webauthdialog.h webauthdialog.ui
)
if(WIN32)
diff --git a/examples/webenginewidgets/simplebrowser/doc/src/simplebrowser.qdoc b/examples/webenginewidgets/simplebrowser/doc/src/simplebrowser.qdoc
index f45b362df..afec5e7e0 100644
--- a/examples/webenginewidgets/simplebrowser/doc/src/simplebrowser.qdoc
+++ b/examples/webenginewidgets/simplebrowser/doc/src/simplebrowser.qdoc
@@ -321,6 +321,30 @@
finished or when an error occurs. See \c downloadmanagerwidget.cpp for an
example of how these signals can be handled.
+ \section1 Managing WebAuth/FIDO UX Requests
+
+ WebAuth UX requests are associated with \l QWebEnginePage. Whenever an authenticator
+ requires user interaction, a UX request is triggered on the QWebEnginePage and
+ the \l QWebEnginePage::webAuthUXRequested signal is emitted with
+ \l QWebEngineWebAuthUXRequest, which in this example is forwarded
+ to \c WebView::handleAuthenticatorRequired:
+
+ \quotefromfile webenginewidgets/simplebrowser/webview.cpp
+ \skipto connect(page, &QWebEnginePage::webAuthUXRequested
+ \printline connect(page, &QWebEnginePage::webAuthUXRequested
+
+ This method creates a WebAuth UX dialog and initiates the UX request flow.
+
+ \quotefromfile webenginewidgets/simplebrowser/webview.cpp
+ \skipto void WebView::handleWebAuthUXRequested(QWebEngineWebAuthUXRequest *request)
+ \printuntil /^\}/
+
+ The \l QWebEngineWebAuthUXRequest object periodically emits the \l
+ {QWebEngineWebAuthUXRequest::}{stateChanged} signal to notify potential
+ observers of the current WebAuth UX states. The observers update the WebAuth
+ dialog accordingly. See \c webview.cpp and \c webauthdialog.cpp for an example
+ of how these signals can be handled.
+
\section1 Files and Attributions
The example uses icons from the Tango Icon Library:
diff --git a/examples/webenginewidgets/simplebrowser/simplebrowser.pro b/examples/webenginewidgets/simplebrowser/simplebrowser.pro
index d4cce7738..8598d237a 100644
--- a/examples/webenginewidgets/simplebrowser/simplebrowser.pro
+++ b/examples/webenginewidgets/simplebrowser/simplebrowser.pro
@@ -10,7 +10,8 @@ HEADERS += \
tabwidget.h \
webpage.h \
webpopupwindow.h \
- webview.h
+ webview.h \
+ webauthdialog.h
SOURCES += \
browser.cpp \
@@ -21,7 +22,8 @@ SOURCES += \
tabwidget.cpp \
webpage.cpp \
webpopupwindow.cpp \
- webview.cpp
+ webview.cpp \
+ webauthdialog.cpp
win32 {
CONFIG -= embed_manifest_exe
@@ -32,7 +34,8 @@ FORMS += \
certificateerrordialog.ui \
passworddialog.ui \
downloadmanagerwidget.ui \
- downloadwidget.ui
+ downloadwidget.ui \
+ webauthdialog.ui
RESOURCES += data/simplebrowser.qrc
diff --git a/examples/webenginewidgets/simplebrowser/webauthdialog.cpp b/examples/webenginewidgets/simplebrowser/webauthdialog.cpp
new file mode 100644
index 000000000..f153667fe
--- /dev/null
+++ b/examples/webenginewidgets/simplebrowser/webauthdialog.cpp
@@ -0,0 +1,294 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "webauthdialog.h"
+
+#include <QVBoxLayout>
+#include <QRadioButton>
+#include <QLineEdit>
+#include <QTextEdit>
+#include <QPushButton>
+#include <QWebEngineView>
+
+WebAuthDialog::WebAuthDialog(QWebEngineWebAuthUXRequest *request, QWidget *parent)
+ : QDialog(parent), uxRequest(request), uiWebAuthDialog(new Ui::WebAuthDialog)
+{
+ uiWebAuthDialog->setupUi(this);
+
+ buttonGroup = new QButtonGroup(this);
+ buttonGroup->setExclusive(true);
+
+ scrollArea = new QScrollArea(this);
+ selectAccountWidget = new QWidget(this);
+ scrollArea->setWidget(selectAccountWidget);
+ scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
+ selectAccountWidget->resize(400, 150);
+ selectAccountLayout = new QVBoxLayout(selectAccountWidget);
+ uiWebAuthDialog->m_mainVerticalLayout->addWidget(scrollArea);
+ selectAccountLayout->setAlignment(Qt::AlignTop);
+
+ updateDisplay();
+
+ connect(uiWebAuthDialog->buttonBox, &QDialogButtonBox::rejected, this,
+ qOverload<>(&WebAuthDialog::onCancelRequest));
+
+ connect(uiWebAuthDialog->buttonBox, &QDialogButtonBox::accepted, this,
+ qOverload<>(&WebAuthDialog::onAcceptRequest));
+ QAbstractButton *button = uiWebAuthDialog->buttonBox->button(QDialogButtonBox::Retry);
+ connect(button, &QAbstractButton::clicked, this, qOverload<>(&WebAuthDialog::onRetry));
+ setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
+}
+
+WebAuthDialog::~WebAuthDialog()
+{
+ QList<QAbstractButton *> buttons = buttonGroup->buttons();
+ auto itr = buttons.begin();
+ while (itr != buttons.end()) {
+ QAbstractButton *radioButton = *itr;
+ delete radioButton;
+ itr++;
+ }
+
+ if (buttonGroup) {
+ delete buttonGroup;
+ buttonGroup = nullptr;
+ }
+
+ if (uiWebAuthDialog) {
+ delete uiWebAuthDialog;
+ uiWebAuthDialog = nullptr;
+ }
+
+ // selectAccountWidget and it's children will get deleted when scroll area is destroyed
+ if (scrollArea) {
+ delete scrollArea;
+ scrollArea = nullptr;
+ }
+}
+
+void WebAuthDialog::updateDisplay()
+{
+ switch (uxRequest->state()) {
+ case QWebEngineWebAuthUXRequest::SelectAccount:
+ setupSelectAccountUI();
+ break;
+ case QWebEngineWebAuthUXRequest::CollectPIN:
+ setupCollectPINUI();
+ break;
+ case QWebEngineWebAuthUXRequest::FinishTokenCollection:
+ setupFinishCollectTokenUI();
+ break;
+ case QWebEngineWebAuthUXRequest::RequestFailed:
+ setupErrorUI();
+ break;
+ default:
+ break;
+ }
+ adjustSize();
+}
+
+void WebAuthDialog::setupSelectAccountUI()
+{
+ uiWebAuthDialog->m_headingLabel->setText(tr("Choose a Passkey"));
+ uiWebAuthDialog->m_description->setText(tr("Which passkey do you want to use for ")
+ + uxRequest->relyingPartyId() + tr("? "));
+ uiWebAuthDialog->m_pinGroupBox->setVisible(false);
+ uiWebAuthDialog->m_mainVerticalLayout->removeWidget(uiWebAuthDialog->m_pinGroupBox);
+ uiWebAuthDialog->buttonBox->button(QDialogButtonBox::Retry)->setVisible(false);
+
+ clearSelectAccountButtons();
+ scrollArea->setVisible(true);
+ selectAccountWidget->resize(this->width(), this->height());
+ QStringList userNames = uxRequest->userNames();
+ // Create radio buttons for each name
+ for (const QString &name : userNames) {
+ QRadioButton *radioButton = new QRadioButton(name);
+ selectAccountLayout->addWidget(radioButton);
+ buttonGroup->addButton(radioButton);
+ }
+
+ uiWebAuthDialog->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Ok"));
+ uiWebAuthDialog->buttonBox->button(QDialogButtonBox::Ok)->setVisible(true);
+ uiWebAuthDialog->buttonBox->button(QDialogButtonBox::Cancel)->setVisible(true);
+ uiWebAuthDialog->buttonBox->button(QDialogButtonBox::Retry)->setVisible(false);
+}
+
+void WebAuthDialog::setupFinishCollectTokenUI()
+{
+ clearSelectAccountButtons();
+ uiWebAuthDialog->m_headingLabel->setText(tr("Use your security key with")
+ + uxRequest->relyingPartyId());
+ uiWebAuthDialog->m_description->setText(
+ tr("Touch your security key again to complete the request."));
+ uiWebAuthDialog->m_pinGroupBox->setVisible(false);
+ uiWebAuthDialog->buttonBox->button(QDialogButtonBox::Ok)->setVisible(false);
+ uiWebAuthDialog->buttonBox->button(QDialogButtonBox::Retry)->setVisible(false);
+ scrollArea->setVisible(false);
+}
+void WebAuthDialog::setupCollectPINUI()
+{
+ clearSelectAccountButtons();
+ uiWebAuthDialog->m_mainVerticalLayout->addWidget(uiWebAuthDialog->m_pinGroupBox);
+ uiWebAuthDialog->m_pinGroupBox->setVisible(true);
+ uiWebAuthDialog->m_confirmPinLabel->setVisible(false);
+ uiWebAuthDialog->m_confirmPinLineEdit->setVisible(false);
+ uiWebAuthDialog->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Next"));
+ uiWebAuthDialog->buttonBox->button(QDialogButtonBox::Ok)->setVisible(true);
+ uiWebAuthDialog->buttonBox->button(QDialogButtonBox::Cancel)->setVisible(true);
+ uiWebAuthDialog->buttonBox->button(QDialogButtonBox::Retry)->setVisible(false);
+ scrollArea->setVisible(false);
+
+ QWebEngineWebAuthPINRequest pinRequestInfo = uxRequest->pinRequest();
+
+ if (pinRequestInfo.reason == QWebEngineWebAuthUXRequest::PINEntryReason::Challenge) {
+ uiWebAuthDialog->m_headingLabel->setText(tr("PIN Required"));
+ uiWebAuthDialog->m_description->setText(tr("Enter the PIN for your security key"));
+ uiWebAuthDialog->m_confirmPinLabel->setVisible(false);
+ uiWebAuthDialog->m_confirmPinLineEdit->setVisible(false);
+ } else {
+ if (pinRequestInfo.reason == QWebEngineWebAuthUXRequest::PINEntryReason::Set) {
+ uiWebAuthDialog->m_headingLabel->setText(tr("New PIN Required"));
+ uiWebAuthDialog->m_description->setText(tr("Set new PIN for your security key"));
+ } else {
+ uiWebAuthDialog->m_headingLabel->setText(tr("Change PIN Required"));
+ uiWebAuthDialog->m_description->setText(tr("Change PIN for your security key"));
+ }
+ uiWebAuthDialog->m_confirmPinLabel->setVisible(true);
+ uiWebAuthDialog->m_confirmPinLineEdit->setVisible(true);
+ }
+
+ QString errorDetails;
+ switch (pinRequestInfo.error) {
+ case QWebEngineWebAuthUXRequest::PINEntryError::NoError:
+ break;
+ case QWebEngineWebAuthUXRequest::PINEntryError::InternalUvLocked:
+ errorDetails = tr("Internal User Verification Locked ");
+ break;
+ case QWebEngineWebAuthUXRequest::PINEntryError::WrongPIN:
+ errorDetails = tr("Wrong Pin");
+ break;
+ case QWebEngineWebAuthUXRequest::PINEntryError::TooShort:
+ errorDetails = tr("Too Short");
+ break;
+ case QWebEngineWebAuthUXRequest::PINEntryError::InvalidCharacters:
+ errorDetails = tr("Invalid Characters");
+ break;
+ case QWebEngineWebAuthUXRequest::PINEntryError::SameAsCurrentPIN:
+ errorDetails = tr("Same as current PIN");
+ break;
+ }
+ if (!errorDetails.isEmpty()) {
+ errorDetails += tr(" ") + QString::number(pinRequestInfo.remainingAttempts)
+ + tr(" attempts remaining");
+ }
+ uiWebAuthDialog->m_pinEntryErrorLabel->setText(errorDetails);
+}
+
+void WebAuthDialog::onCancelRequest()
+{
+ uxRequest->cancel();
+}
+
+void WebAuthDialog::onAcceptRequest()
+{
+ switch (uxRequest->state()) {
+ case QWebEngineWebAuthUXRequest::SelectAccount:
+ if (buttonGroup->checkedButton()) {
+ uxRequest->setSelectedAccount(buttonGroup->checkedButton()->text());
+ }
+ break;
+ case QWebEngineWebAuthUXRequest::CollectPIN:
+ uxRequest->setPin(uiWebAuthDialog->m_pinLineEdit->text());
+ break;
+ default:
+ break;
+ }
+}
+
+void WebAuthDialog::setupErrorUI()
+{
+ clearSelectAccountButtons();
+ QString errorDescription;
+ QString errorHeading = tr("Something went wrong");
+ bool isVisibleRetry = false;
+ switch (uxRequest->requestFailureReason()) {
+ case QWebEngineWebAuthUXRequest::RequestFailureReason::Timeout:
+ errorDescription = tr("Request Timeout");
+ break;
+ case QWebEngineWebAuthUXRequest::RequestFailureReason::KeyNotRegistered:
+ errorDescription = tr("Key not registered");
+ break;
+ case QWebEngineWebAuthUXRequest::RequestFailureReason::KeyAlreadyRegistered:
+ errorDescription = tr("You already registered this device."
+ "Try again with device");
+ isVisibleRetry = true;
+ break;
+ case QWebEngineWebAuthUXRequest::RequestFailureReason::SoftPINBlock:
+ errorDescription =
+ tr("The security key is locked because the wrong PIN was entered too many times."
+ "To unlock it, remove and reinsert it.");
+ isVisibleRetry = true;
+ break;
+ case QWebEngineWebAuthUXRequest::RequestFailureReason::HardPINBlock:
+ errorDescription =
+ tr("The security key is locked because the wrong PIN was entered too many times."
+ " You'll need to reset the security key.");
+ break;
+ case QWebEngineWebAuthUXRequest::RequestFailureReason::AuthenticatorRemovedDuringPINEntry:
+ errorDescription =
+ tr("Authenticator removed during verification. Please reinsert and try again");
+ break;
+ case QWebEngineWebAuthUXRequest::RequestFailureReason::AuthenticatorMissingResidentKeys:
+ errorDescription = tr("Authenticator doesn't have resident key support");
+ break;
+ case QWebEngineWebAuthUXRequest::RequestFailureReason::AuthenticatorMissingUserVerification:
+ errorDescription = tr("Authenticator missing user verification");
+ break;
+ case QWebEngineWebAuthUXRequest::RequestFailureReason::AuthenticatorMissingLargeBlob:
+ errorDescription = tr("Authenticator missing Large Blob support");
+ break;
+ case QWebEngineWebAuthUXRequest::RequestFailureReason::NoCommonAlgorithms:
+ errorDescription = tr("Authenticator missing Large Blob support");
+ break;
+ case QWebEngineWebAuthUXRequest::RequestFailureReason::StorageFull:
+ errorDescription = tr("Storage Full");
+ break;
+ case QWebEngineWebAuthUXRequest::RequestFailureReason::UserConsentDenied:
+ errorDescription = tr("User consent denied");
+ break;
+ case QWebEngineWebAuthUXRequest::RequestFailureReason::WinUserCancelled:
+ errorDescription = tr("User Cancelled Request");
+ break;
+ }
+
+ uiWebAuthDialog->m_headingLabel->setText(errorHeading);
+ uiWebAuthDialog->m_description->setText(errorDescription);
+ uiWebAuthDialog->m_description->adjustSize();
+ uiWebAuthDialog->m_pinGroupBox->setVisible(false);
+ uiWebAuthDialog->buttonBox->button(QDialogButtonBox::Ok)->setVisible(false);
+ uiWebAuthDialog->buttonBox->button(QDialogButtonBox::Retry)->setVisible(isVisibleRetry);
+ if (isVisibleRetry)
+ uiWebAuthDialog->buttonBox->button(QDialogButtonBox::Retry)->setFocus();
+ uiWebAuthDialog->buttonBox->button(QDialogButtonBox::Cancel)->setVisible(true);
+ uiWebAuthDialog->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Close"));
+ scrollArea->setVisible(false);
+}
+
+void WebAuthDialog::onRetry()
+{
+ uxRequest->retry();
+}
+
+void WebAuthDialog::clearSelectAccountButtons()
+{
+ QList<QAbstractButton *> buttons = buttonGroup->buttons();
+ auto itr = buttons.begin();
+ while (itr != buttons.end()) {
+ QAbstractButton *radioButton = *itr;
+ selectAccountLayout->removeWidget(radioButton);
+ buttonGroup->removeButton(radioButton);
+ delete radioButton;
+ itr++;
+ }
+}
diff --git a/examples/webenginewidgets/simplebrowser/webauthdialog.h b/examples/webenginewidgets/simplebrowser/webauthdialog.h
new file mode 100644
index 000000000..352bcd0d4
--- /dev/null
+++ b/examples/webenginewidgets/simplebrowser/webauthdialog.h
@@ -0,0 +1,41 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef WEBAUTHDIALOG_H
+#define WEBAUTHDIALOG_H
+
+#include <QDialog>
+#include <QButtonGroup>
+#include <QScrollArea>
+#include "ui_webauthdialog.h"
+#include "qwebenginewebauthuxrequest.h"
+
+class WebAuthDialog : public QDialog
+{
+ Q_OBJECT
+public:
+ WebAuthDialog(QWebEngineWebAuthUXRequest *request, QWidget *parent = nullptr);
+ ~WebAuthDialog();
+
+ void updateDisplay();
+
+private:
+ QWebEngineWebAuthUXRequest *uxRequest;
+ QButtonGroup *buttonGroup = nullptr;
+ QScrollArea *scrollArea = nullptr;
+ QWidget *selectAccountWidget = nullptr;
+ QVBoxLayout *selectAccountLayout = nullptr;
+
+ void setupSelectAccountUI();
+ void setupCollectPINUI();
+ void setupFinishCollectTokenUI();
+ void setupErrorUI();
+ void onCancelRequest();
+ void onRetry();
+ void onAcceptRequest();
+ void clearSelectAccountButtons();
+
+ Ui::WebAuthDialog *uiWebAuthDialog;
+};
+
+#endif // WEBAUTHDIALOG_H
diff --git a/examples/webenginewidgets/simplebrowser/webauthdialog.ui b/examples/webenginewidgets/simplebrowser/webauthdialog.ui
new file mode 100644
index 000000000..c8a0456d6
--- /dev/null
+++ b/examples/webenginewidgets/simplebrowser/webauthdialog.ui
@@ -0,0 +1,151 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>WebAuthDialog</class>
+ <widget class="QDialog" name="WebAuthDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>563</width>
+ <height>397</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Dialog</string>
+ </property>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="geometry">
+ <rect>
+ <x>20</x>
+ <y>320</y>
+ <width>471</width>
+ <height>32</height>
+ </rect>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Retry</set>
+ </property>
+ </widget>
+ <widget class="QLabel" name="m_headingLabel">
+ <property name="geometry">
+ <rect>
+ <x>30</x>
+ <y>20</y>
+ <width>321</width>
+ <height>16</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>Heading</string>
+ </property>
+ <property name="wordWrap">
+ <bool>false</bool>
+ </property>
+ </widget>
+ <widget class="QLabel" name="m_description">
+ <property name="geometry">
+ <rect>
+ <x>30</x>
+ <y>60</y>
+ <width>491</width>
+ <height>31</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>Description</string>
+ </property>
+ <property name="wordWrap">
+ <bool>false</bool>
+ </property>
+ </widget>
+ <widget class="QWidget" name="layoutWidget">
+ <property name="geometry">
+ <rect>
+ <x>20</x>
+ <y>100</y>
+ <width>471</width>
+ <height>171</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout" name="m_mainVerticalLayout">
+ <property name="sizeConstraint">
+ <enum>QLayout::SetDefaultConstraint</enum>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="m_pinGroupBox">
+ <property name="title">
+ <string/>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ <widget class="QLabel" name="m_pinLabel">
+ <property name="geometry">
+ <rect>
+ <x>10</x>
+ <y>20</y>
+ <width>58</width>
+ <height>16</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>PIN</string>
+ </property>
+ </widget>
+ <widget class="QLineEdit" name="m_pinLineEdit">
+ <property name="geometry">
+ <rect>
+ <x>90</x>
+ <y>20</y>
+ <width>113</width>
+ <height>21</height>
+ </rect>
+ </property>
+ </widget>
+ <widget class="QLabel" name="m_confirmPinLabel">
+ <property name="geometry">
+ <rect>
+ <x>10</x>
+ <y>50</y>
+ <width>81</width>
+ <height>16</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>Confirm PIN</string>
+ </property>
+ </widget>
+ <widget class="QLineEdit" name="m_confirmPinLineEdit">
+ <property name="geometry">
+ <rect>
+ <x>90</x>
+ <y>50</y>
+ <width>113</width>
+ <height>21</height>
+ </rect>
+ </property>
+ </widget>
+ <widget class="QLabel" name="m_pinEntryErrorLabel">
+ <property name="geometry">
+ <rect>
+ <x>10</x>
+ <y>80</y>
+ <width>441</width>
+ <height>16</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>TextLabel</string>
+ </property>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/examples/webenginewidgets/simplebrowser/webview.cpp b/examples/webenginewidgets/simplebrowser/webview.cpp
index e024f9126..a47cb23a3 100644
--- a/examples/webenginewidgets/simplebrowser/webview.cpp
+++ b/examples/webenginewidgets/simplebrowser/webview.cpp
@@ -9,6 +9,7 @@
#include "webview.h"
#include "ui_certificateerrordialog.h"
#include "ui_passworddialog.h"
+#include "webauthdialog.h"
#include <QContextMenuEvent>
#include <QDebug>
#include <QMenu>
@@ -98,6 +99,8 @@ void WebView::setPage(WebPage *page)
&WebView::handleProxyAuthenticationRequired);
disconnect(oldPage, &QWebEnginePage::registerProtocolHandlerRequested, this,
&WebView::handleRegisterProtocolHandlerRequested);
+ disconnect(oldPage, &QWebEnginePage::webAuthUXRequested, this,
+ &WebView::handleWebAuthUXRequested);
#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
disconnect(oldPage, &QWebEnginePage::fileSystemAccessRequested, this,
&WebView::handleFileSystemAccessRequested);
@@ -121,6 +124,7 @@ void WebView::setPage(WebPage *page)
connect(page, &QWebEnginePage::fileSystemAccessRequested, this,
&WebView::handleFileSystemAccessRequested);
#endif
+ connect(page, &QWebEnginePage::webAuthUXRequested, this, &WebView::handleWebAuthUXRequested);
}
int WebView::loadProgress() const
@@ -292,6 +296,32 @@ void WebView::handleProxyAuthenticationRequired(const QUrl &, QAuthenticator *au
}
}
+void WebView::handleWebAuthUXRequested(QWebEngineWebAuthUXRequest *request)
+{
+ if (m_authDialog)
+ delete m_authDialog;
+
+ m_authDialog = new WebAuthDialog(request, window());
+ m_authDialog->setModal(false);
+ m_authDialog->setWindowFlags(m_authDialog->windowFlags() & ~Qt::WindowContextHelpButtonHint);
+
+ connect(request, &QWebEngineWebAuthUXRequest::stateChanged, this, &WebView::onStateChanged);
+ m_authDialog->show();
+}
+
+void WebView::onStateChanged(QWebEngineWebAuthUXRequest::WebAuthUXState state)
+{
+ if (QWebEngineWebAuthUXRequest::Completed == state
+ || QWebEngineWebAuthUXRequest::Cancelled == state) {
+ if (m_authDialog) {
+ delete m_authDialog;
+ m_authDialog = nullptr;
+ }
+ } else {
+ m_authDialog->updateDisplay();
+ }
+}
+
//! [registerProtocolHandlerRequested]
void WebView::handleRegisterProtocolHandlerRequested(
QWebEngineRegisterProtocolHandlerRequest request)
diff --git a/examples/webenginewidgets/simplebrowser/webview.h b/examples/webenginewidgets/simplebrowser/webview.h
index 41bc04ac0..bb4b1f2ad 100644
--- a/examples/webenginewidgets/simplebrowser/webview.h
+++ b/examples/webenginewidgets/simplebrowser/webview.h
@@ -12,8 +12,10 @@
#endif
#include <QWebEnginePage>
#include <QWebEngineRegisterProtocolHandlerRequest>
+#include <QWebEngineWebAuthUXRequest>
class WebPage;
+class WebAuthDialog;
class WebView : public QWebEngineView
{
@@ -45,13 +47,16 @@ private slots:
void handleRegisterProtocolHandlerRequested(QWebEngineRegisterProtocolHandlerRequest request);
#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
void handleFileSystemAccessRequested(QWebEngineFileSystemAccessRequest request);
+ void handleWebAuthUXRequested(QWebEngineWebAuthUXRequest *request);
#endif
private:
void createWebActionTrigger(QWebEnginePage *page, QWebEnginePage::WebAction);
+ void onStateChanged(QWebEngineWebAuthUXRequest::WebAuthUXState state);
private:
int m_loadProgress = 100;
+ WebAuthDialog *m_authDialog = nullptr;
};
#endif
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 02d2584a6..cbcbf0100 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -196,6 +196,8 @@ foreach(arch ${archs})
web_engine_settings.cpp web_engine_settings.h
web_event_factory.cpp web_event_factory.h
web_usb_detector_qt.cpp web_usb_detector_qt.h
+ authenticator_request_client_delegate_qt.cpp authenticator_request_client_delegate_qt.h
+ authenticator_request_dialog_controller.cpp authenticator_request_dialog_controller.h authenticator_request_dialog_controller_p.h
)
extend_gn_target(${buildGn} CONDITION QT_FEATURE_accessibility
diff --git a/src/core/api/CMakeLists.txt b/src/core/api/CMakeLists.txt
index 208ccf10d..564d7b382 100644
--- a/src/core/api/CMakeLists.txt
+++ b/src/core/api/CMakeLists.txt
@@ -39,6 +39,7 @@ qt_internal_add_module(WebEngineCore
qwebengineurlscheme.cpp qwebengineurlscheme.h
qwebengineurlschemehandler.cpp qwebengineurlschemehandler.h
qwebengineglobalsettings.cpp qwebengineglobalsettings.h qwebengineglobalsettings_p.h
+ qwebenginewebauthuxrequest.cpp qwebenginewebauthuxrequest.h qwebenginewebauthuxrequest_p.h
DEFINES
BUILDING_CHROMIUM
INCLUDE_DIRECTORIES
diff --git a/src/core/api/qwebenginepage.cpp b/src/core/api/qwebenginepage.cpp
index 4c0b7b8ff..0c60300e1 100644
--- a/src/core/api/qwebenginepage.cpp
+++ b/src/core/api/qwebenginepage.cpp
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qwebenginepage.h"
+#include "authenticator_request_dialog_controller.h"
#include "qwebenginepage_p.h"
#include "qwebenginecertificateerror.h"
@@ -21,6 +22,7 @@
#include "qwebenginescript.h"
#include "qwebenginescriptcollection_p.h"
#include "qwebenginesettings.h"
+#include "qwebenginewebauthuxrequest.h"
#include "authentication_dialog_controller.h"
#include "autofill_popup_controller.h"
@@ -776,6 +778,12 @@ void QWebEnginePagePrivate::ensureInitialized() const
adapter->loadDefault();
}
+void QWebEnginePagePrivate::showWebAuthDialog(QWebEngineWebAuthUXRequest *request)
+{
+ Q_Q(QWebEnginePage);
+ Q_EMIT q->webAuthUXRequested(request);
+}
+
QWebEnginePage::QWebEnginePage(QObject* parent)
: QObject(parent)
, d_ptr(new QWebEnginePagePrivate())
diff --git a/src/core/api/qwebenginepage.h b/src/core/api/qwebenginepage.h
index d0f1c70ee..8603d1065 100644
--- a/src/core/api/qwebenginepage.h
+++ b/src/core/api/qwebenginepage.h
@@ -41,6 +41,7 @@ class QWebEngineScriptCollection;
class QWebEngineSettings;
class QWebEngineUrlRequestInterceptor;
class QWebEngineUrlResponseInterceptor;
+class QWebEngineWebAuthUXRequest;
class Q_WEBENGINECORE_EXPORT QWebEnginePage : public QObject
{
@@ -355,6 +356,8 @@ Q_SIGNALS:
// TODO: fixme / rewrite bindPageToView
void _q_aboutToDelete();
+ void webAuthUXRequested(QWebEngineWebAuthUXRequest *request);
+
protected:
virtual QWebEnginePage *createWindow(WebWindowType type);
virtual QStringList chooseFiles(FileSelectionMode mode, const QStringList &oldFiles,
diff --git a/src/core/api/qwebenginepage_p.h b/src/core/api/qwebenginepage_p.h
index 532a9b942..a261e1d38 100644
--- a/src/core/api/qwebenginepage_p.h
+++ b/src/core/api/qwebenginepage_p.h
@@ -171,6 +171,7 @@ public:
void showAutofillPopup(QtWebEngineCore::AutofillPopupController *controller,
const QRect &bounds, bool autoselectFirstSuggestion) override;
void hideAutofillPopup() override;
+ void showWebAuthDialog(QWebEngineWebAuthUXRequest *controller) override;
QtWebEngineCore::ProfileAdapter *profileAdapter() override;
QtWebEngineCore::WebContentsAdapter *webContentsAdapter() override;
diff --git a/src/core/api/qwebenginewebauthuxrequest.cpp b/src/core/api/qwebenginewebauthuxrequest.cpp
new file mode 100644
index 000000000..4cd9e0ad4
--- /dev/null
+++ b/src/core/api/qwebenginewebauthuxrequest.cpp
@@ -0,0 +1,431 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qwebenginewebauthuxrequest.h"
+#include "qwebenginewebauthuxrequest_p.h"
+#include "authenticator_request_dialog_controller.h"
+
+/*!
+ \qmltype WebEngineWebAuthUXRequest
+ \instantiates QWebEngineWebAuthUXRequest
+ \inqmlmodule QtWebEngine
+ \since QtWebEngine 6.7
+ \brief Encapsulates the data of a WebAuth UX request.
+
+ Web engine's WebAuth UX requests are passed to the user in the
+ \l WebEngineView::webAuthUXRequested() signal.
+
+ For more information about how to handle web engine authenticator requests, see the
+ \l{WebEngine Quick Nano Browser}{Nano Browser}.
+*/
+
+/*!
+ \class QWebEngineWebAuthUXRequest
+ \brief The QWebEngineWebAuthUXRequest class encapsulates the data of a WebAuth UX request.
+ \since 6.7
+
+ \inmodule QtWebEngineCore
+
+ This class contains the information and API for WebAuth UX. WebAuth may require user
+ interaction during the authentication process. These requests are handled by displaying a
+ dialog to users. QtWebEngine currently supports user verification, resident credentials,
+ and display request failure UX requests.
+
+ QWebEngineWebAuthUXRequest models a WebAuth UX request throughout its life cycle,
+ starting with showing a UX dialog, updating it's content through state changes, and
+ finally closing the dialog.
+
+ WebAuth UX requests are normally triggered when the authenticator requires user interaction.
+ It is the QWebEnginePage's responsibility to notify the application of the new WebAuth UX
+ requests, which it does by emitting the
+ \l{QWebEnginePage::webAuthUXRequested}{webAuthUXRequested} signal together with a newly
+ created QWebEngineWebAuthUXRequest. The application can then examine this request and
+ display a WebAuth UX dialog.
+
+ The QWebEngineWebAuthUXRequest object periodically emits the \l
+ {QWebEngineWebAuthUXRequest::}{stateChanged} signal to notify potential
+ observers of the current WebAuth UX states. The observers update the WebAuth dialog
+ accordingly.
+
+ For more information about how to handle web engine authenticator requests, see the
+ \l{WebEngine Widgets Simple Browser Example}{Simple Browser}.
+*/
+
+/*!
+ \struct QWebEngineWebAuthPINRequest
+ \brief The QWebEngineWebAuthPINRequest class encapsulates the data of a PIN WebAuth UX request.
+ \since 6.7
+
+ \inmodule QtWebEngineCore
+
+ This encapsulates the following information related to a PIN request made by an authenticator.
+ \list
+ \li The reason for the PIN prompt.
+ \li The error details for the PIN prompt.
+ \li The number of attempts remaining before a hard lock. Should be ignored unless
+ \l{QWebEngineWebAuthPINRequest::reason} is
+ \l{QWebEngineWebAuthUXRequest::PINEntryReason::Challenge}.
+ \li The minimum PIN length the authenticator will accept for the PIN.
+ \endlist
+ Use this structure to update the WebAuth UX dialog when the WebAuth UX state is \l
+ QWebEngineWebAuthUXRequest::CollectPIN.
+*/
+
+/*!
+ \property QWebEngineWebAuthPINRequest::reason
+ \brief The reason for the PIN prompt.
+*/
+
+/*!
+ \property QWebEngineWebAuthPINRequest::error
+ \brief The error details for the PIN prompt.
+*/
+
+/*!
+ \property QWebEngineWebAuthPINRequest::remainingAttempts
+ \brief The number of attempts remaining before a hard lock. Should be ignored unless
+ \l{QWebEngineWebAuthPINRequest::reason} is
+ \l{QWebEngineWebAuthUXRequest::PINEntryReason::Challenge}.
+*/
+
+/*!
+ \property QWebEngineWebAuthPINRequest::minPinLength
+ \brief The minimum PIN length the authenticator will accept for the PIN.
+*/
+
+/*!
+ \enum QWebEngineWebAuthUXRequest::WebAuthUXState
+
+ This enum describes the state of the current WebAuth UX request.
+
+ \value NotStarted WebAuth UX request not started yet.
+ \value SelectAccount The authenticator requires resident credential details.
+ The application needs to display an account details dialog, and
+ the user needs to select an account to proceed.
+ \value CollectPIN The authenticator requires user verification.
+ The application needs to display a PIN request dialog.
+ \value FinishTokenCollection The authenticator requires token/user verification (like tap on
+ the FIDO key) to complete the process.
+ \value RequestFailed WebAuth request failed. Display error details.
+ \value Cancelled WebAuth request is cancelled. Close the WebAuth dialog.
+ \value Completed WebAuth request is completed. Close the WebAuth dialog.
+*/
+
+/*!
+ \enum QWebEngineWebAuthUXRequest::PINEntryReason
+
+ This enum describes the reasons that may prompt the authenticator to ask for a PIN.
+
+ \value Set A new PIN is being set.
+ \value Change The existing PIN must be changed before using this authenticator.
+ \value Challenge The existing PIN is being collected to prove user verification.
+*/
+
+/*!
+ \enum QWebEngineWebAuthUXRequest::PINEntryError
+
+ This enum describes the errors that may prompt the authenticator to ask for a PIN.
+
+ \value NoError No error has occurred.
+ \value InternalUvLocked Internal UV is locked, so we are falling back to PIN.
+ \value WrongPIN The PIN the user entered does not match the authenticator PIN.
+ \value TooShort The new PIN the user entered is too short.
+ \value InvalidCharacters The new PIN the user entered contains invalid characters.
+ \value SameAsCurrentPIN The new PIN the user entered is the same as the currently set PIN.
+*/
+
+/*!
+ \enum QWebEngineWebAuthUXRequest::RequestFailureReason
+
+ This enum describes the reason for WebAuth request failure.
+
+ \value Timeout The authentication session has timed out.
+ \value KeyNotRegistered Key is not registered with the authenticator.
+ \value KeyAlreadyRegistered Key is already registered with the authenticator.
+ Try to register with another Key or use another authenticator.
+ \value SoftPINBlock The authenticator is blocked as the user entered the wrong key many times.
+ \value HardPINBlock The authenticator is blocked as the user entered the wrong key many times
+ and reset the PIN to use the specific authenticator again.
+ \value AuthenticatorRemovedDuringPINEntry Authenticator removed during PIN entry.
+ \value AuthenticatorMissingResidentKeys Authenticator doesn't have resident key support.
+ \value AuthenticatorMissingUserVerification Authenticator doesn't
+ have user verification support.
+ \value AuthenticatorMissingLargeBlob Authenticator doesn't have large blob support.
+ \value NoCommonAlgorithms No common algorithm.
+ \value StorageFull The resident credential could not be created because the
+ authenticator has insufficient storage.
+ \value UserConsentDenied User consent denied.
+ \value WinUserCancelled The user clicked \uicontrol Cancel in the native windows UI.
+*/
+
+/*!
+ \fn void QWebEngineWebAuthUXRequest::stateChanged(WebAuthUXState state)
+
+ This signal is emitted whenever the WebAuth UX's \a state changes.
+
+ \sa state, WebAuthUXState
+*/
+
+/*!
+ \qmlsignal void WebEngineWebAuthUXRequest::stateChanged(WebAuthUXState state)
+ This signal is emitted whenever the WebAuth UX's \a state changes.
+
+ \sa state, QWebEngineWebAuthUXRequest::WebAuthUXState
+*/
+
+/*! \internal
+ */
+QWebEngineWebAuthUXRequestPrivate::QWebEngineWebAuthUXRequestPrivate(
+ QtWebEngineCore::AuthenticatorRequestDialogController *controller)
+ : webAuthDialogController(controller)
+{
+ m_currentState = controller->state();
+}
+
+/*! \internal
+ */
+QWebEngineWebAuthUXRequestPrivate::~QWebEngineWebAuthUXRequestPrivate() { }
+
+/*! \internal
+ */
+void QWebEngineWebAuthUXRequest::handleUXUpdate(WebAuthUXState currentState)
+{
+ Q_D(QWebEngineWebAuthUXRequest);
+
+ d->m_currentState = currentState;
+
+ Q_EMIT stateChanged(d->m_currentState);
+}
+
+/*! \internal
+ */
+QWebEngineWebAuthUXRequest::QWebEngineWebAuthUXRequest(QWebEngineWebAuthUXRequestPrivate *p)
+ : d_ptr(p)
+{
+ connect(d_ptr->webAuthDialogController,
+ &QtWebEngineCore::AuthenticatorRequestDialogController::stateChanged, this,
+ &QWebEngineWebAuthUXRequest::handleUXUpdate);
+}
+
+/*! \internal
+ */
+QWebEngineWebAuthUXRequest::~QWebEngineWebAuthUXRequest() { }
+
+/*!
+ \qmlproperty stringlist WebEngineWebAuthUXRequest::userNames
+ \brief The available user names for the resident credential support.
+
+ This is needed when the current WebAuth request's UX state is
+ WebEngineWebAuthUXRequest.SelectAccount. The WebAuth dialog displays user names.
+ The user needs to select an account to proceed.
+
+ \sa state setSelectedAccount() QWebEngineWebAuthUXRequest::userNames
+*/
+/*!
+ \property QWebEngineWebAuthUXRequest::userNames
+ \brief The available user names for the resident credential support.
+ This is needed when the current WebAuth request's UX state is \l SelectAccount.
+ The WebAuth dialog displays user names. The user needs to select an account to proceed.
+
+ \sa SelectAccount setSelectedAccount()
+*/
+QStringList QWebEngineWebAuthUXRequest::userNames() const
+{
+ const Q_D(QWebEngineWebAuthUXRequest);
+
+ return d->webAuthDialogController->userNames();
+}
+
+/*!
+ \qmlproperty string WebEngineWebAuthUXRequest::relyingPartyId
+ \brief The WebAuth request's relying party id.
+*/
+/*!
+ \property QWebEngineWebAuthUXRequest::relyingPartyId
+ \brief The WebAuth request's relying party id.
+*/
+QString QWebEngineWebAuthUXRequest::relyingPartyId() const
+{
+ const Q_D(QWebEngineWebAuthUXRequest);
+
+ return d->webAuthDialogController->relyingPartyId();
+}
+
+/*!
+ \qmlproperty QWebEngineWebAuthPINRequest WebEngineWebAuthUXRequest::pinRequest
+ \brief The WebAuth request's PIN request information.
+
+ \sa QWebEngineWebAuthPINRequest
+*/
+/*!
+ \property QWebEngineWebAuthUXRequest::pinRequest
+ \brief The WebAuth request's PIN request information.
+
+ This is needed when the current WebAuth request state is \l CollectPIN.
+ WebAuth Dialog displays a PIN request dialog. The user needs to enter a PIN and
+ invoke \l setPin() to proceed.
+
+ \sa QWebEngineWebAuthPINRequest CollectPIN setPin()
+*/
+QWebEngineWebAuthPINRequest QWebEngineWebAuthUXRequest::pinRequest() const
+{
+ const Q_D(QWebEngineWebAuthUXRequest);
+
+ return d->webAuthDialogController->pinRequest();
+}
+
+/*!
+ \qmlproperty enumeration WebEngineWebAuthUXRequest::state
+ \brief The WebAuth request's current UX state.
+
+ \value WebEngineWebAuthUXRequest.NotStarted WebAuth UX request not started yet.
+ \value WebEngineWebAuthUXRequest.SelectAccount The authenticator requires
+ resident credential details. The application needs to display an account details dialog,
+ and the user needs to select an account to proceed.
+ \value WebEngineWebAuthUXRequest.CollectPIN The authenticator requires user verification.
+ The application needs to display a PIN request dialog.
+ \value WebEngineWebAuthUXRequest.FinishTokenCollection The authenticator requires
+ token/user verification (like tap on the FIDO key) to complete the process.
+ \value WebEngineWebAuthUXRequest.RequestFailed WebAuth request failed. Display error details.
+ \value WebEngineWebAuthUXRequest.Cancelled WebAuth request is cancelled.
+ Close the WebAuth dialog.
+ \value WebEngineWebAuthUXRequest.Completed WebAuth request is completed.
+ Close the WebAuth dialog.
+*/
+/*!
+ \property QWebEngineWebAuthUXRequest::state
+ \brief The WebAuth request's current UX state.
+
+ \l stateChanged() is emitted when the current state changes.
+ Update the WebAuth dialog in reponse to the changes in state.
+*/
+QWebEngineWebAuthUXRequest::WebAuthUXState QWebEngineWebAuthUXRequest::state() const
+{
+ return d_ptr->m_currentState;
+}
+
+/*!
+ \qmlmethod void WebEngineWebAuthUXRequest::setSelectedAccount(const QString &selectedAccount)
+ Sends the \a selectedAccount name to the authenticator.
+ This is needed when the current WebAuth request's UX state is
+ WebEngineWebAuthUXRequest.SelectAccount. The WebAuth request is blocked until the user selects
+ an account and invokes this method.
+
+ \sa WebEngineWebAuthUXRequest::userNames state
+*/
+/*!
+ Sends the \a selectedAccount name to the authenticator.
+ This is needed when the current WebAuth request's UX state is \l SelectAccount.
+ The WebAuth request is blocked until the user selects an account and invokes this method.
+
+ \sa userNames SelectAccount
+*/
+void QWebEngineWebAuthUXRequest::setSelectedAccount(const QString &selectedAccount)
+{
+ Q_D(QWebEngineWebAuthUXRequest);
+
+ d->webAuthDialogController->sendSelectAccountResponse(selectedAccount);
+}
+
+/*!
+ \qmlmethod void WebEngineWebAuthUXRequest::setPin(const QString &pin)
+ Sends the \a pin to the authenticator that prompts for a PIN.
+ This is needed when the current WebAuth request's UX state is
+ WebEngineWebAuthUXRequest.CollectPIN. The WebAuth request is blocked until
+ the user responds with a PIN.
+
+ \sa QWebEngineWebAuthPINRequest state
+*/
+/*!
+ Sends the \a pin to the authenticator that prompts for a PIN.
+ This is needed when the current WebAuth request's UX state is \l CollectPIN.
+ The WebAuth request is blocked until the user responds with a PIN.
+
+ \sa QWebEngineWebAuthPINRequest CollectPIN
+*/
+void QWebEngineWebAuthUXRequest::setPin(const QString &pin)
+{
+ Q_D(QWebEngineWebAuthUXRequest);
+ d->webAuthDialogController->sendCollectPinResponse(pin);
+}
+
+/*!
+ \qmlmethod void WebEngineWebAuthUXRequest::cancel()
+ Cancels the current WebAuth request.
+
+ \sa QWebEngineWebAuthUXRequest::Cancelled, WebEngineWebAuthUXRequest::stateChanged()
+*/
+/*!
+ Cancels the current WebAuth request.
+
+ \sa QWebEngineWebAuthUXRequest::Cancelled, stateChanged()
+*/
+void QWebEngineWebAuthUXRequest::cancel()
+{
+ Q_D(QWebEngineWebAuthUXRequest);
+
+ d->webAuthDialogController->reject();
+}
+
+/*!
+ \qmlmethod void WebEngineWebAuthUXRequest::retry()
+ Retries the current WebAuth request.
+
+ \sa stateChanged()
+*/
+/*!
+ Retries the current WebAuth request.
+
+ \sa stateChanged()
+*/
+void QWebEngineWebAuthUXRequest::retry()
+{
+ const Q_D(QWebEngineWebAuthUXRequest);
+
+ d->webAuthDialogController->retryRequest();
+}
+
+/*!
+ \qmlproperty enumeration WebEngineWebAuthUXRequest::requestFailureReason
+ \brief The WebAuth request's failure reason.
+
+ \value WebEngineWebAuthUXRequest.Timeout The authentication session has timed out.
+ \value WebEngineWebAuthUXRequest.KeyNotRegistered Key is not registered with the authenticator.
+ \value WebEngineWebAuthUXRequest.KeyAlreadyRegistered Key is already registered with
+ the authenticator. Try to register with another key or use another authenticator.
+ \value WebEngineWebAuthUXRequest.SoftPINBlock The authenticator is blocked as the user
+ entered the wrong key many times.
+ \value WebEngineWebAuthUXRequest.HardPINBlock The authenticator is blocked as the user entered
+ the wrong key many times and reset the PIN to use the specific authenticator again.
+ \value WebEngineWebAuthUXRequest.AuthenticatorRemovedDuringPINEntry Authenticator
+ removed during PIN entry.
+ \value WebEngineWebAuthUXRequest.AuthenticatorMissingResidentKeys Authenticator doesn't
+ have resident key support.
+ \value WebEngineWebAuthUXRequest.AuthenticatorMissingUserVerification Authenticator doesn't
+ have user verification support.
+ \value WebEngineWebAuthUXRequest.AuthenticatorMissingLargeBlob Authenticator doesn't have
+ large blob support.
+ \value WebEngineWebAuthUXRequest.NoCommonAlgorithms No common algorithm.
+ \value WebEngineWebAuthUXRequest.StorageFull The resident credential could not be created
+ because the authenticator has insufficient storage.
+ \value WebEngineWebAuthUXRequest.UserConsentDenied User consent denied.
+ \value WebEngineWebAuthUXRequest.WinUserCancelled The user clicked \uicontrol Cancel
+ in the native windows UI.
+
+ \sa stateChanged()
+*/
+/*!
+ \property QWebEngineWebAuthUXRequest::requestFailureReason
+ \brief The WebAuth request's failure reason.
+
+ \sa stateChanged() QWebEngineWebAuthUXRequest::RequestFailureReason
+*/
+QWebEngineWebAuthUXRequest::RequestFailureReason
+QWebEngineWebAuthUXRequest::requestFailureReason() const
+{
+ const Q_D(QWebEngineWebAuthUXRequest);
+
+ return d->webAuthDialogController->requestFailureReason();
+}
+
+#include "moc_qwebenginewebauthuxrequest.cpp"
diff --git a/src/core/api/qwebenginewebauthuxrequest.h b/src/core/api/qwebenginewebauthuxrequest.h
new file mode 100644
index 000000000..b4a99539c
--- /dev/null
+++ b/src/core/api/qwebenginewebauthuxrequest.h
@@ -0,0 +1,145 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QWEBENGINEWEBAUTHUXREQUEST_H
+#define QWEBENGINEWEBAUTHUXREQUEST_H
+
+#include <QtWebEngineCore/qtwebenginecoreglobal.h>
+#include <QtCore/qobject.h>
+
+QT_BEGIN_NAMESPACE
+
+class QWebEngineWebAuthUXRequestPrivate;
+struct QWebEngineWebAuthPINRequest;
+
+class Q_WEBENGINECORE_EXPORT QWebEngineWebAuthUXRequest : public QObject
+{
+ Q_OBJECT
+public:
+ QWebEngineWebAuthUXRequest(QWebEngineWebAuthUXRequestPrivate *);
+ ~QWebEngineWebAuthUXRequest();
+
+ enum WebAuthUXState {
+ NotStarted,
+ SelectAccount,
+ CollectPIN,
+ FinishTokenCollection,
+ RequestFailed,
+ Cancelled,
+ Completed
+ };
+ Q_ENUM(WebAuthUXState)
+
+ // The reason we are prompting for a new PIN.
+ enum class PINEntryReason : int {
+ // Indicates a new PIN is being set.
+ Set,
+
+ // The existing PIN must be changed before using this authenticator.
+ Change,
+
+ // The existing PIN is being collected to prove user verification.
+ Challenge
+ };
+ Q_ENUM(PINEntryReason)
+
+ // The errors that may prompt asking for a PIN.
+ enum class PINEntryError : int {
+ // No error has occurred.
+ NoError,
+
+ // Internal UV is locked, so we are falling back to PIN.
+ InternalUvLocked,
+
+ // The PIN the user entered does not match the authenticator PIN.
+ WrongPIN,
+
+ // The new PIN the user entered is too short.
+ TooShort,
+
+ // The new PIN the user entered contains invalid characters.
+ InvalidCharacters,
+
+ // The new PIN the user entered is the same as the currently set PIN.
+ SameAsCurrentPIN,
+ };
+ Q_ENUM(PINEntryError)
+
+ enum class RequestFailureReason : int {
+ Timeout,
+ KeyNotRegistered,
+ KeyAlreadyRegistered,
+ SoftPINBlock,
+ HardPINBlock,
+ AuthenticatorRemovedDuringPINEntry,
+ AuthenticatorMissingResidentKeys,
+ AuthenticatorMissingUserVerification,
+ AuthenticatorMissingLargeBlob,
+ NoCommonAlgorithms,
+ // kStorageFull indicates that a resident credential could not be created
+ // because the authenticator has insufficient storage.
+ StorageFull,
+ UserConsentDenied,
+ // kWinUserCancelled means that the user clicked "Cancel" in the native
+ // Windows UI.
+ WinUserCancelled,
+ };
+ Q_ENUM(RequestFailureReason)
+
+ Q_PROPERTY(QStringList userNames READ userNames CONSTANT FINAL)
+ Q_PROPERTY(WebAuthUXState state READ state NOTIFY stateChanged FINAL)
+ Q_PROPERTY(QString relyingPartyId READ relyingPartyId CONSTANT FINAL)
+ Q_PROPERTY(QWebEngineWebAuthPINRequest pinRequest READ pinRequest CONSTANT FINAL)
+ Q_PROPERTY(RequestFailureReason requestFailureReason READ requestFailureReason CONSTANT FINAL)
+
+ QStringList userNames() const;
+ QString relyingPartyId() const;
+ QWebEngineWebAuthPINRequest pinRequest() const;
+ WebAuthUXState state() const;
+ RequestFailureReason requestFailureReason() const;
+
+Q_SIGNALS:
+ void stateChanged(QWebEngineWebAuthUXRequest::WebAuthUXState state);
+
+public Q_SLOTS:
+ void cancel();
+ void retry();
+ void setSelectedAccount(const QString &selectedAccount);
+ void setPin(const QString &pin);
+
+private Q_SLOTS:
+ void handleUXUpdate(WebAuthUXState currentState);
+
+protected:
+ QScopedPointer<QWebEngineWebAuthUXRequestPrivate> d_ptr;
+
+ Q_DECLARE_PRIVATE(QWebEngineWebAuthUXRequest)
+};
+
+struct Q_WEBENGINECORE_EXPORT QWebEngineWebAuthPINRequest
+{
+ Q_GADGET
+
+ Q_PROPERTY(QWebEngineWebAuthUXRequest::PINEntryReason reason MEMBER reason CONSTANT FINAL)
+ Q_PROPERTY(QWebEngineWebAuthUXRequest::PINEntryError error MEMBER error CONSTANT FINAL)
+ Q_PROPERTY(qint32 minPinLength MEMBER minPinLength CONSTANT FINAL)
+ Q_PROPERTY(qint32 remainingAttempts MEMBER remainingAttempts CONSTANT FINAL)
+public:
+ // Why this PIN is being collected.
+ QWebEngineWebAuthUXRequest::PINEntryReason reason;
+
+ // The error for which we are prompting for a PIN.
+ QWebEngineWebAuthUXRequest::PINEntryError error =
+ QWebEngineWebAuthUXRequest::PINEntryError::NoError;
+
+ // The minimum PIN length the authenticator will accept for the PIN.
+ qint32 minPinLength;
+
+ // The number of attempts remaining before a hard lock. Should be ignored unless |mode| is
+ // kChallenge.
+ int remainingAttempts = 0;
+};
+
+QT_END_NAMESPACE
+
+#endif // QWEBENGINEWEBAUTHUXREQUEST_H
diff --git a/src/core/api/qwebenginewebauthuxrequest_p.h b/src/core/api/qwebenginewebauthuxrequest_p.h
new file mode 100644
index 000000000..45280bb0d
--- /dev/null
+++ b/src/core/api/qwebenginewebauthuxrequest_p.h
@@ -0,0 +1,43 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QWEBENGINEWEBAUTHUXREQUEST_P_H
+#define QWEBENGINEWEBAUTHUXREQUEST_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qtwebenginecoreglobal_p.h"
+#include "qwebenginewebauthuxrequest.h"
+#include <QtCore/QSharedPointer>
+
+namespace QtWebEngineCore {
+class WebContentsAdapterClient;
+class AuthenticatorRequestDialogController;
+}
+
+QT_BEGIN_NAMESPACE
+
+class Q_WEBENGINECORE_PRIVATE_EXPORT QWebEngineWebAuthUXRequestPrivate
+{
+
+public:
+ QWebEngineWebAuthUXRequestPrivate(
+ QtWebEngineCore::AuthenticatorRequestDialogController *controller);
+ ~QWebEngineWebAuthUXRequestPrivate();
+
+ QWebEngineWebAuthUXRequest::WebAuthUXState m_currentState =
+ QWebEngineWebAuthUXRequest::NotStarted;
+ QtWebEngineCore::AuthenticatorRequestDialogController *webAuthDialogController;
+};
+
+QT_END_NAMESPACE
+#endif // QWEBENGINEWEBAUTHUXREQUEST_P_H
diff --git a/src/core/authenticator_request_client_delegate_qt.cpp b/src/core/authenticator_request_client_delegate_qt.cpp
new file mode 100644
index 000000000..e999b74ab
--- /dev/null
+++ b/src/core/authenticator_request_client_delegate_qt.cpp
@@ -0,0 +1,246 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "authenticator_request_client_delegate_qt.h"
+#include "authenticator_request_dialog_controller.h"
+#include "authenticator_request_dialog_controller_p.h"
+#include "base/base64.h"
+#include "profile_adapter_client.h"
+#include "profile_qt.h"
+#include "content/public/browser/web_contents.h"
+#include "web_contents_delegate_qt.h"
+#include "type_conversion.h"
+
+namespace QtWebEngineCore {
+
+using RequestFailureReason = QWebEngineWebAuthUXRequest::RequestFailureReason;
+
+WebAuthenticationDelegateQt::WebAuthenticationDelegateQt() = default;
+
+WebAuthenticationDelegateQt::~WebAuthenticationDelegateQt() = default;
+
+bool WebAuthenticationDelegateQt::SupportsResidentKeys(content::RenderFrameHost *render_frame_host)
+{
+ return true;
+}
+
+AuthenticatorRequestClientDelegateQt::AuthenticatorRequestClientDelegateQt(
+ content::RenderFrameHost *render_frame_host)
+ : m_renderFrameHost(render_frame_host), m_weakFactory(this)
+{
+ m_dialogController.reset(new AuthenticatorRequestDialogController(
+ new AuthenticatorRequestDialogControllerPrivate(m_renderFrameHost,
+ m_weakFactory.GetWeakPtr())));
+}
+
+AuthenticatorRequestClientDelegateQt::~AuthenticatorRequestClientDelegateQt()
+{
+ // Currently WebAuth request is completed. Now it is possible to delete the dialog if displayed
+ m_dialogController->finishRequest();
+}
+
+void AuthenticatorRequestClientDelegateQt::SetRelyingPartyId(const std::string &rp_id)
+{
+ m_dialogController->setRelyingPartyId(rp_id);
+}
+
+bool AuthenticatorRequestClientDelegateQt::DoesBlockRequestOnFailure(
+ InterestingFailureReason reason)
+{
+ if (!IsWebAuthnUIEnabled())
+ return false;
+
+ switch (reason) {
+ case InterestingFailureReason::kTimeout:
+ m_dialogController->handleRequestFailure(RequestFailureReason::Timeout);
+ break;
+ case InterestingFailureReason::kAuthenticatorMissingResidentKeys:
+ m_dialogController->handleRequestFailure(
+ RequestFailureReason::AuthenticatorMissingResidentKeys);
+ break;
+ case InterestingFailureReason::kAuthenticatorMissingUserVerification:
+ m_dialogController->handleRequestFailure(
+ RequestFailureReason::AuthenticatorMissingUserVerification);
+ break;
+ case InterestingFailureReason::kAuthenticatorMissingLargeBlob:
+ m_dialogController->handleRequestFailure(
+ RequestFailureReason::AuthenticatorMissingLargeBlob);
+ break;
+ case InterestingFailureReason::kAuthenticatorRemovedDuringPINEntry:
+ m_dialogController->handleRequestFailure(
+ RequestFailureReason::AuthenticatorRemovedDuringPINEntry);
+ break;
+ case InterestingFailureReason::kHardPINBlock:
+ m_dialogController->handleRequestFailure(RequestFailureReason::HardPINBlock);
+ break;
+ case InterestingFailureReason::kSoftPINBlock:
+ m_dialogController->handleRequestFailure(RequestFailureReason::SoftPINBlock);
+ break;
+ case InterestingFailureReason::kKeyAlreadyRegistered:
+ m_dialogController->handleRequestFailure(RequestFailureReason::KeyAlreadyRegistered);
+ break;
+ case InterestingFailureReason::kKeyNotRegistered:
+ m_dialogController->handleRequestFailure(RequestFailureReason::KeyNotRegistered);
+ break;
+ case InterestingFailureReason::kNoCommonAlgorithms:
+ m_dialogController->handleRequestFailure(RequestFailureReason::NoCommonAlgorithms);
+ break;
+ case InterestingFailureReason::kStorageFull:
+ m_dialogController->handleRequestFailure(RequestFailureReason::StorageFull);
+ break;
+ case InterestingFailureReason::kUserConsentDenied:
+ m_dialogController->handleRequestFailure(RequestFailureReason::UserConsentDenied);
+ break;
+ case InterestingFailureReason::kWinUserCancelled:
+#if BUILDFLAG(IS_WIN)
+ m_dialogController->handleRequestFailure(RequestFailureReason::WinUserCancelled);
+#else
+ return false;
+#endif
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+void AuthenticatorRequestClientDelegateQt::RegisterActionCallbacks(
+ base::OnceClosure cancel_callback, base::RepeatingClosure start_over_callback,
+ AccountPreselectedCallback account_preselected_callback,
+ device::FidoRequestHandlerBase::RequestCallback request_callback,
+ base::RepeatingClosure bluetooth_adapter_power_on_callback)
+{
+ m_cancelCallback = std::move(cancel_callback);
+ m_startOverCallback = std::move(start_over_callback);
+ m_accountPreselectedCallback = std::move(account_preselected_callback);
+ m_requestCallback = std::move(request_callback);
+ m_bluetoothAdapterPowerOnCallback = std::move(bluetooth_adapter_power_on_callback);
+}
+
+void AuthenticatorRequestClientDelegateQt::ShouldReturnAttestation(
+ const std::string &relying_party_id, const device::FidoAuthenticator *authenticator,
+ bool is_enterprise_attestation, base::OnceCallback<void(bool)> callback)
+{
+ std::move(callback).Run(!is_enterprise_attestation);
+}
+
+void AuthenticatorRequestClientDelegateQt::SelectAccount(
+ std::vector<device::AuthenticatorGetAssertionResponse> responses,
+ base::OnceCallback<void(device::AuthenticatorGetAssertionResponse)> callback)
+{
+ if (m_isUiDisabled) {
+ // Requests with UI disabled should never reach account selection.
+ DCHECK(IsVirtualEnvironmentEnabled());
+ std::move(callback).Run(std::move(responses.at(0)));
+ return;
+ }
+
+ if (m_isConditionalRequest) {
+ return;
+ }
+
+ m_authenticatorGetAssertionResponse = std::move(responses);
+ m_selectAccountCallback = std::move(callback);
+
+ QStringList userList;
+ int nIndex = -1;
+ for (const auto &response : m_authenticatorGetAssertionResponse) {
+ nIndex++;
+ const auto &user_entity = response.user_entity;
+ const bool has_user_identifying_info = user_entity && user_entity->name;
+ if (has_user_identifying_info) {
+ QString userName = toQt(*response.user_entity->name);
+ m_userMap[userName] = nIndex;
+ userList.append(userName);
+ }
+ }
+ m_dialogController->selectAccount(userList);
+}
+
+void AuthenticatorRequestClientDelegateQt::DisableUI()
+{
+ m_isUiDisabled = true;
+}
+
+bool AuthenticatorRequestClientDelegateQt::IsWebAuthnUIEnabled()
+{
+ return !m_isUiDisabled;
+}
+
+void AuthenticatorRequestClientDelegateQt::SetConditionalRequest(bool is_conditional)
+{
+ m_isConditionalRequest = is_conditional;
+}
+
+// This method will not be invoked until the observer is set.
+void AuthenticatorRequestClientDelegateQt::OnTransportAvailabilityEnumerated(
+ device::FidoRequestHandlerBase::TransportAvailabilityInfo data)
+{
+ // Show dialog only after this step;
+ // If m_isUiDisabled is set or another UI request in progress return
+ if (m_isUiDisabled || m_dialogController->state() != QWebEngineWebAuthUXRequest::NotStarted)
+ return;
+
+ // Start WebAuth UX
+ // we may need to pass data as well. for SelectAccount and SupportPin it is not required,
+ // skipping that for the timebeing.
+ m_dialogController->startRequest(m_isConditionalRequest);
+}
+
+bool AuthenticatorRequestClientDelegateQt::SupportsPIN() const
+{
+ return true;
+}
+
+void AuthenticatorRequestClientDelegateQt::CollectPIN(
+ CollectPINOptions options, base::OnceCallback<void(std::u16string)> provide_pin_cb)
+{
+
+ m_providePinCallback = std::move(provide_pin_cb);
+ QWebEngineWebAuthPINRequest pinRequestInfo;
+
+ pinRequestInfo.reason = static_cast<QWebEngineWebAuthUXRequest::PINEntryReason>(options.reason);
+ pinRequestInfo.error = static_cast<QWebEngineWebAuthUXRequest::PINEntryError>(options.error);
+ pinRequestInfo.remainingAttempts = options.attempts;
+ pinRequestInfo.minPinLength = options.min_pin_length;
+ m_dialogController->collectPIN(pinRequestInfo);
+}
+
+void AuthenticatorRequestClientDelegateQt::FinishCollectToken()
+{
+ m_dialogController->finishCollectToken();
+}
+
+void AuthenticatorRequestClientDelegateQt::onCancelRequest()
+{
+ if (!m_cancelCallback)
+ return;
+
+ std::move(m_cancelCallback).Run();
+}
+
+void AuthenticatorRequestClientDelegateQt::onSelectAccount(const QString &selectedAccount)
+{
+ if (!m_selectAccountCallback)
+ return;
+
+ if (m_userMap.find(selectedAccount) != m_userMap.end()) {
+ std::move(m_selectAccountCallback)
+ .Run(std::move(m_authenticatorGetAssertionResponse.at(m_userMap[selectedAccount])));
+ } else {
+ onCancelRequest();
+ }
+}
+
+void AuthenticatorRequestClientDelegateQt::onCollectPin(const QString &pin)
+{
+ if (!m_providePinCallback)
+ return;
+ std::move(m_providePinCallback).Run(pin.toStdU16String());
+}
+
+void AuthenticatorRequestClientDelegateQt::onRetryRequest()
+{
+ DCHECK(m_startOverCallback);
+ m_startOverCallback.Run();
+}
+}
diff --git a/src/core/authenticator_request_client_delegate_qt.h b/src/core/authenticator_request_client_delegate_qt.h
new file mode 100644
index 000000000..05c6136bd
--- /dev/null
+++ b/src/core/authenticator_request_client_delegate_qt.h
@@ -0,0 +1,96 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef AUTHENTICATOR_REQUEST_CLIENT_DELEGATE_QT_H
+#define AUTHENTICATOR_REQUEST_CLIENT_DELEGATE_QT_H
+
+#include "qtwebenginecoreglobal_p.h"
+#include "content/public/browser/authenticator_request_client_delegate.h"
+#include <unordered_map>
+#include <QSharedPointer>
+
+namespace QtWebEngineCore {
+
+class AuthenticatorRequestDialogController;
+
+class WebAuthenticationDelegateQt : public content::WebAuthenticationDelegate
+{
+public:
+ WebAuthenticationDelegateQt();
+ virtual ~WebAuthenticationDelegateQt();
+
+ bool SupportsResidentKeys(content::RenderFrameHost *render_frame_host) override;
+};
+
+class AuthenticatorRequestClientDelegateQt : public content::AuthenticatorRequestClientDelegate
+{
+public:
+ explicit AuthenticatorRequestClientDelegateQt(content::RenderFrameHost *render_frame_host);
+ AuthenticatorRequestClientDelegateQt(const AuthenticatorRequestClientDelegateQt &) = delete;
+ AuthenticatorRequestClientDelegateQt &
+ operator=(const AuthenticatorRequestClientDelegateQt &) = delete;
+ ~AuthenticatorRequestClientDelegateQt();
+
+ // content::AuthenticatorRequestClientDelegate ovverrides
+ void SetRelyingPartyId(const std::string &rp_id) override;
+ bool DoesBlockRequestOnFailure(InterestingFailureReason reason) override;
+ void RegisterActionCallbacks(base::OnceClosure cancel_callback,
+ base::RepeatingClosure start_over_callback,
+ AccountPreselectedCallback account_preselected_callback,
+ device::FidoRequestHandlerBase::RequestCallback request_callback,
+ base::RepeatingClosure bluetooth_adapter_power_on_callback) override;
+ void ShouldReturnAttestation(const std::string &relying_party_id,
+ const device::FidoAuthenticator *authenticator,
+ bool is_enterprise_attestation,
+ base::OnceCallback<void(bool)> callback) override;
+ void SelectAccount(
+ std::vector<device::AuthenticatorGetAssertionResponse> responses,
+ base::OnceCallback<void(device::AuthenticatorGetAssertionResponse)> callback) override;
+ void DisableUI() override;
+ bool IsWebAuthnUIEnabled() override;
+ void SetConditionalRequest(bool is_conditional) override;
+
+ // device::FidoRequestHandlerBase::Observer overrides:
+ // This method will not be invoked until the observer is set.
+ void OnTransportAvailabilityEnumerated(
+ device::FidoRequestHandlerBase::TransportAvailabilityInfo data) override;
+
+ bool SupportsPIN() const override;
+ void CollectPIN(CollectPINOptions options,
+ base::OnceCallback<void(std::u16string)> provide_pin_cb) override;
+ void FinishCollectToken() override;
+
+ // Dialog helper
+ void onCancelRequest();
+ void onSelectAccount(const QString &selectedAccount);
+ void onCollectPin(const QString &pin);
+ void onRetryRequest();
+
+private:
+ content::RenderFrameHost *m_renderFrameHost;
+ bool m_isUiDisabled = false;
+ bool m_isConditionalRequest = false;
+
+ base::OnceClosure m_cancelCallback;
+ base::RepeatingClosure m_startOverCallback;
+ AccountPreselectedCallback m_accountPreselectedCallback;
+ device::FidoRequestHandlerBase::RequestCallback m_requestCallback;
+ base::RepeatingClosure m_bluetoothAdapterPowerOnCallback;
+
+ // Select account details;
+ std::vector<device::AuthenticatorGetAssertionResponse> m_authenticatorGetAssertionResponse;
+ std::unordered_map<QString, int> m_userMap;
+ base::OnceCallback<void(device::AuthenticatorGetAssertionResponse)> m_selectAccountCallback;
+
+ // collect pin
+ base::OnceCallback<void(std::u16string)> m_providePinCallback;
+
+ // This member is used to keep authenticator request dialog controller alive until
+ // authenticator request is completed or cancelled.
+ QSharedPointer<AuthenticatorRequestDialogController> m_dialogController;
+ base::WeakPtrFactory<AuthenticatorRequestClientDelegateQt> m_weakFactory;
+};
+
+}
+
+#endif // AUTHENTICATOR_REQUEST_CLIENT_DELEGATE_QT_H
diff --git a/src/core/authenticator_request_dialog_controller.cpp b/src/core/authenticator_request_dialog_controller.cpp
new file mode 100644
index 000000000..f71aeaa39
--- /dev/null
+++ b/src/core/authenticator_request_dialog_controller.cpp
@@ -0,0 +1,301 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "authenticator_request_dialog_controller.h"
+#include "authenticator_request_dialog_controller_p.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "web_contents_delegate_qt.h"
+#include "qwebenginewebauthuxrequest_p.h"
+#include "qwebenginewebauthuxrequest.h"
+
+using PINEntryError = QWebEngineWebAuthUXRequest::PINEntryError;
+using PINEntryReason = QWebEngineWebAuthUXRequest::PINEntryReason;
+
+namespace QtWebEngineCore {
+
+ASSERT_ENUMS_MATCH(PINEntryReason::Set, device::pin::PINEntryReason::kSet)
+ASSERT_ENUMS_MATCH(PINEntryReason::Change, device::pin::PINEntryReason::kChange)
+ASSERT_ENUMS_MATCH(PINEntryReason::Challenge, device::pin::PINEntryReason::kChallenge)
+ASSERT_ENUMS_MATCH(PINEntryError::WrongPIN, device::pin::PINEntryError::kWrongPIN)
+ASSERT_ENUMS_MATCH(PINEntryError::TooShort, device::pin::PINEntryError::kTooShort)
+ASSERT_ENUMS_MATCH(PINEntryError::SameAsCurrentPIN, device::pin::PINEntryError::kSameAsCurrentPIN)
+ASSERT_ENUMS_MATCH(PINEntryError::NoError, device::pin::PINEntryError::kNoError)
+ASSERT_ENUMS_MATCH(PINEntryError::InvalidCharacters, device::pin::PINEntryError::kInvalidCharacters)
+ASSERT_ENUMS_MATCH(PINEntryError::InternalUvLocked, device::pin::PINEntryError::kInternalUvLocked)
+
+AuthenticatorRequestDialogControllerPrivate::AuthenticatorRequestDialogControllerPrivate(
+ content::RenderFrameHost *renderFrameHost,
+ base::WeakPtr<AuthenticatorRequestClientDelegateQt> authenticatorRequestDelegate)
+ : m_renderFrameHost(renderFrameHost)
+ , m_authenticatorRequestDelegate(authenticatorRequestDelegate)
+{
+}
+
+AuthenticatorRequestDialogControllerPrivate::~AuthenticatorRequestDialogControllerPrivate()
+{
+ if (m_request) {
+ delete m_request;
+ m_request = nullptr;
+ }
+}
+
+void AuthenticatorRequestDialogControllerPrivate::showWebAuthDialog()
+{
+ Q_ASSERT(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+
+ content::WebContents *webContent = content::WebContents::FromRenderFrameHost(m_renderFrameHost);
+
+ if (!webContent)
+ return;
+
+ WebContentsAdapterClient *adapterClient = nullptr;
+ if (webContent)
+ adapterClient =
+ static_cast<WebContentsDelegateQt *>(webContent->GetDelegate())->adapterClient();
+
+ if (adapterClient) {
+
+ QWebEngineWebAuthUXRequestPrivate *itemPrivate =
+ new QWebEngineWebAuthUXRequestPrivate(q_ptr);
+
+ m_request = new QWebEngineWebAuthUXRequest(itemPrivate);
+
+ adapterClient->showWebAuthDialog(m_request);
+ m_isDialogCreated = true;
+ } else {
+ cancelRequest();
+ }
+}
+
+void AuthenticatorRequestDialogControllerPrivate::selectAccount(const QStringList &userList)
+{
+ m_userList.clear();
+ m_userList = userList;
+ setCurrentState(QWebEngineWebAuthUXRequest::SelectAccount);
+}
+
+void AuthenticatorRequestDialogControllerPrivate::collectPIN(QWebEngineWebAuthPINRequest pinRequest)
+{
+ m_pinRequest = pinRequest;
+ setCurrentState(QWebEngineWebAuthUXRequest::CollectPIN);
+}
+
+void AuthenticatorRequestDialogControllerPrivate::finishCollectToken()
+{
+ setCurrentState(QWebEngineWebAuthUXRequest::FinishTokenCollection);
+}
+
+QStringList AuthenticatorRequestDialogControllerPrivate::userNames() const
+{
+ return m_userList;
+}
+
+void AuthenticatorRequestDialogControllerPrivate::finishRequest()
+{
+ if (!m_isDialogCreated)
+ return;
+ setCurrentState(QWebEngineWebAuthUXRequest::Completed);
+}
+
+void AuthenticatorRequestDialogControllerPrivate::setCurrentState(
+ QWebEngineWebAuthUXRequest::WebAuthUXState uxState)
+{
+ if (!m_isStarted) {
+ // Dialog isn't showing yet. Remember to show this step when it appears.
+ m_pendingState = uxState;
+ return;
+ }
+
+ m_currentState = uxState;
+
+ if (m_isConditionalRequest)
+ return;
+
+ if (!m_isDialogCreated) {
+ showWebAuthDialog();
+ } else {
+ Q_EMIT q_ptr->stateChanged(m_currentState);
+
+ if (m_currentState == QWebEngineWebAuthUXRequest::Cancelled
+ || m_currentState == QWebEngineWebAuthUXRequest::Completed) {
+ m_isDialogCreated = false;
+ }
+ }
+}
+
+void AuthenticatorRequestDialogControllerPrivate::cancelRequest()
+{
+ setCurrentState(QWebEngineWebAuthUXRequest::Cancelled);
+ content::GetUIThreadTaskRunner({})->PostTask(
+ FROM_HERE,
+ base::BindOnce(&AuthenticatorRequestClientDelegateQt::onCancelRequest,
+ m_authenticatorRequestDelegate));
+}
+
+void AuthenticatorRequestDialogControllerPrivate::retryRequest()
+{
+ content::GetUIThreadTaskRunner({})->PostTask(
+ FROM_HERE,
+ base::BindOnce(&AuthenticatorRequestClientDelegateQt::onRetryRequest,
+ m_authenticatorRequestDelegate));
+}
+
+void AuthenticatorRequestDialogControllerPrivate::sendSelectAccountResponse(
+ const QString &selectedAccount)
+{
+ content::GetUIThreadTaskRunner({})->PostTask(
+ FROM_HERE,
+ base::BindOnce(&AuthenticatorRequestClientDelegateQt::onSelectAccount,
+ m_authenticatorRequestDelegate, selectedAccount));
+}
+
+QWebEngineWebAuthUXRequest::WebAuthUXState
+AuthenticatorRequestDialogControllerPrivate::state() const
+{
+ return m_currentState;
+}
+
+void AuthenticatorRequestDialogControllerPrivate::startRequest(bool isConditionalRequest)
+{
+ DCHECK(!m_isStarted);
+
+ m_isStarted = true;
+ m_isConditionalRequest = isConditionalRequest;
+
+ if (m_pendingState) {
+ setCurrentState(*m_pendingState);
+ m_pendingState.reset();
+ }
+}
+
+void AuthenticatorRequestDialogControllerPrivate::setRelyingPartyId(const QString &rpId)
+{
+ m_relyingPartyId = rpId;
+}
+
+QString AuthenticatorRequestDialogControllerPrivate::relyingPartyId() const
+{
+ return m_relyingPartyId;
+}
+
+QWebEngineWebAuthPINRequest AuthenticatorRequestDialogControllerPrivate::pinRequest()
+{
+ return m_pinRequest;
+}
+
+void AuthenticatorRequestDialogControllerPrivate::handleRequestFailure(
+ QWebEngineWebAuthUXRequest::RequestFailureReason reason)
+{
+ m_requestFailureReason = reason;
+ setCurrentState(QWebEngineWebAuthUXRequest::RequestFailed);
+}
+
+void AuthenticatorRequestDialogControllerPrivate::sendCollectPinResponse(const QString &pin)
+{
+ content::GetUIThreadTaskRunner({})->PostTask(
+ FROM_HERE,
+ base::BindOnce(&AuthenticatorRequestClientDelegateQt::onCollectPin,
+ m_authenticatorRequestDelegate, pin));
+}
+
+QWebEngineWebAuthUXRequest::RequestFailureReason
+AuthenticatorRequestDialogControllerPrivate::requestFailureReason() const
+{
+ return m_requestFailureReason;
+}
+
+AuthenticatorRequestDialogController::AuthenticatorRequestDialogController(
+ AuthenticatorRequestDialogControllerPrivate *dd)
+{
+ Q_ASSERT(dd);
+ d_ptr.reset(dd);
+ d_ptr->q_ptr = this;
+}
+
+AuthenticatorRequestDialogController::~AuthenticatorRequestDialogController() { }
+
+void AuthenticatorRequestDialogController::selectAccount(const QStringList &userList)
+{
+ d_ptr->selectAccount(userList);
+}
+
+void AuthenticatorRequestDialogController::collectPIN(QWebEngineWebAuthPINRequest pinRequest)
+{
+ d_ptr->collectPIN(pinRequest);
+}
+
+QStringList AuthenticatorRequestDialogController::userNames() const
+{
+ return d_ptr->userNames();
+}
+
+QWebEngineWebAuthPINRequest AuthenticatorRequestDialogController::pinRequest()
+{
+ return d_ptr->pinRequest();
+}
+
+void AuthenticatorRequestDialogController::reject()
+{
+ d_ptr->cancelRequest();
+}
+
+void AuthenticatorRequestDialogController::sendSelectAccountResponse(const QString &account)
+{
+ d_ptr->sendSelectAccountResponse(account);
+}
+
+void AuthenticatorRequestDialogController::finishCollectToken()
+{
+ d_ptr->finishCollectToken();
+}
+
+void AuthenticatorRequestDialogController::finishRequest()
+{
+ d_ptr->finishRequest();
+}
+
+QWebEngineWebAuthUXRequest::WebAuthUXState AuthenticatorRequestDialogController::state() const
+{
+ return d_ptr->state();
+}
+
+void AuthenticatorRequestDialogController::startRequest(bool bIsConditionalRequest)
+{
+ d_ptr->startRequest(bIsConditionalRequest);
+}
+
+void AuthenticatorRequestDialogController::setRelyingPartyId(const std::string &rpId)
+{
+ d_ptr->setRelyingPartyId(QString::fromStdString(rpId));
+}
+
+QString AuthenticatorRequestDialogController::relyingPartyId() const
+{
+ return d_ptr->relyingPartyId();
+}
+
+void AuthenticatorRequestDialogController::handleRequestFailure(
+ QWebEngineWebAuthUXRequest::RequestFailureReason reason)
+{
+ d_ptr->handleRequestFailure(reason);
+}
+
+void AuthenticatorRequestDialogController::retryRequest()
+{
+ d_ptr->retryRequest();
+}
+
+void AuthenticatorRequestDialogController::sendCollectPinResponse(const QString &pin)
+{
+ d_ptr->sendCollectPinResponse(pin);
+}
+
+QWebEngineWebAuthUXRequest::RequestFailureReason
+AuthenticatorRequestDialogController::requestFailureReason() const
+{
+ return d_ptr->requestFailureReason();
+}
+}
diff --git a/src/core/authenticator_request_dialog_controller.h b/src/core/authenticator_request_dialog_controller.h
new file mode 100644
index 000000000..6d81fc80d
--- /dev/null
+++ b/src/core/authenticator_request_dialog_controller.h
@@ -0,0 +1,56 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef AUTHENTICATOR_REQUEST_DIALOG_CONTROLLER_H
+#define AUTHENTICATOR_REQUEST_DIALOG_CONTROLLER_H
+
+#include <QtWebEngineCore/private/qtwebenginecoreglobal_p.h>
+#include <QtCore/qobject.h>
+#include "qwebenginewebauthuxrequest.h"
+
+// #include "base/functional/callback_forward.h"
+// #include "device/fido/authenticator_get_assertion_response.h"
+
+namespace content {
+class WebContents;
+class RenderFrameHost;
+}
+namespace QtWebEngineCore {
+
+class AuthenticatorRequestDialogControllerPrivate;
+
+class Q_WEBENGINECORE_EXPORT AuthenticatorRequestDialogController : public QObject
+{
+ Q_OBJECT
+public:
+ ~AuthenticatorRequestDialogController();
+ void sendSelectAccountResponse(const QString &account);
+ void sendCollectPinResponse(const QString &pin);
+ QStringList userNames() const;
+ QWebEngineWebAuthPINRequest pinRequest();
+ void reject();
+ AuthenticatorRequestDialogController(AuthenticatorRequestDialogControllerPrivate *);
+
+ QWebEngineWebAuthUXRequest::WebAuthUXState state() const;
+ QString relyingPartyId() const;
+ void retryRequest();
+ QWebEngineWebAuthUXRequest::RequestFailureReason requestFailureReason() const;
+
+Q_SIGNALS:
+ void stateChanged(QWebEngineWebAuthUXRequest::WebAuthUXState state);
+
+private:
+ void selectAccount(const QStringList &userList);
+ void collectPIN(QWebEngineWebAuthPINRequest pinRequest);
+ void finishCollectToken();
+ void startRequest(bool bIsConditionalRequest);
+ void finishRequest();
+ void setRelyingPartyId(const std::string &rpId);
+ void handleRequestFailure(QWebEngineWebAuthUXRequest::RequestFailureReason reason);
+
+ QScopedPointer<AuthenticatorRequestDialogControllerPrivate> d_ptr;
+ friend class AuthenticatorRequestClientDelegateQt;
+};
+}
+
+#endif // AUTHENTICATOR_REQUEST_DIALOG_CONTROLLER_H
diff --git a/src/core/authenticator_request_dialog_controller_p.h b/src/core/authenticator_request_dialog_controller_p.h
new file mode 100644
index 000000000..abb2710bf
--- /dev/null
+++ b/src/core/authenticator_request_dialog_controller_p.h
@@ -0,0 +1,79 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef AUTHENTICATOR_REQUEST_DIALOG_CONTROLLER_P_H
+#define AUTHENTICATOR_REQUEST_DIALOG_CONTROLLER_P_H
+#include <QStringList>
+#include <QSharedPointer>
+#include "authenticator_request_client_delegate_qt.h"
+#include "authenticator_request_dialog_controller.h"
+
+namespace content {
+class WebContents;
+class RenderFrameHost;
+}
+
+namespace QtWebEngineCore {
+
+class AuthenticatorRequestDialogControllerPrivate
+{
+
+public:
+ AuthenticatorRequestDialogControllerPrivate(
+ content::RenderFrameHost *renderFrameHost,
+ base::WeakPtr<AuthenticatorRequestClientDelegateQt> authenticatorRequestDelegate);
+ ~AuthenticatorRequestDialogControllerPrivate();
+ void showWebAuthDialog();
+ void selectAccount(const QStringList &userList);
+ QStringList userNames() const;
+ QString relyingPartyId() const;
+ QWebEngineWebAuthUXRequest::WebAuthUXState state() const;
+ QWebEngineWebAuthPINRequest pinRequest();
+ QWebEngineWebAuthUXRequest::RequestFailureReason requestFailureReason() const;
+ void sendSelectAccountResponse(const QString &selectedAccount);
+ void setCurrentState(QWebEngineWebAuthUXRequest::WebAuthUXState uxState);
+ void setRelyingPartyId(const QString &rpId);
+
+ // Support pin functionality
+ void collectPIN(QWebEngineWebAuthPINRequest pinRequestInfo);
+ void finishCollectToken();
+ void handleRequestFailure(QWebEngineWebAuthUXRequest::RequestFailureReason reason);
+ void sendCollectPinResponse(const QString &pin);
+
+ // Deleting dialog;
+ void finishRequest();
+
+ // cancel request
+ void cancelRequest();
+ void retryRequest();
+ void startRequest(bool isConditionalRequest);
+
+ AuthenticatorRequestDialogController *q_ptr;
+
+private:
+ content::RenderFrameHost *m_renderFrameHost;
+ QStringList m_userList;
+ // QString m_selectedAccount;
+ QString m_pin;
+ QString m_relyingPartyId;
+
+ bool m_isStarted = false;
+ bool m_isConditionalRequest = false;
+ QWebEngineWebAuthUXRequest::WebAuthUXState m_currentState =
+ QWebEngineWebAuthUXRequest::NotStarted;
+ base::WeakPtr<AuthenticatorRequestClientDelegateQt> m_authenticatorRequestDelegate;
+ bool m_isDialogCreated = false;
+ QWebEngineWebAuthPINRequest m_pinRequest;
+
+ QWebEngineWebAuthUXRequest *m_request;
+ QWebEngineWebAuthUXRequest::RequestFailureReason m_requestFailureReason;
+
+ // m_pendingState holds requested steps until the UI is shown. The UI is only
+ // shown once the TransportAvailabilityInfo is available, but authenticators
+ // may request, e.g., PIN entry prior to that.
+ absl::optional<QWebEngineWebAuthUXRequest::WebAuthUXState> m_pendingState;
+};
+
+} // namespace QtWebEngineCore
+
+#endif // AUTHENTICATOR_REQUEST_DIALOG_CONTROLLER_P_H
diff --git a/src/core/content_browser_client_qt.cpp b/src/core/content_browser_client_qt.cpp
index bf39ebe2f..904ed87a5 100644
--- a/src/core/content_browser_client_qt.cpp
+++ b/src/core/content_browser_client_qt.cpp
@@ -83,6 +83,7 @@
#include "web_engine_context.h"
#include "web_engine_library_info.h"
#include "web_engine_settings.h"
+#include "authenticator_request_client_delegate_qt.h"
#include "api/qwebenginecookiestore.h"
#include "api/qwebenginecookiestore_p.h"
#include "api/qwebengineurlrequestinfo_p.h"
@@ -1350,4 +1351,19 @@ ContentBrowserClientQt::AllowWebBluetooth(content::BrowserContext *browser_conte
return content::ContentBrowserClient::AllowWebBluetoothResult::BLOCK_GLOBALLY_DISABLED;
}
+content::WebAuthenticationDelegate *ContentBrowserClientQt::GetWebAuthenticationDelegate()
+{
+ static base::NoDestructor<WebAuthenticationDelegateQt> delegate;
+ return delegate.get();
+}
+
+#if !BUILDFLAG(IS_ANDROID)
+std::unique_ptr<content::AuthenticatorRequestClientDelegate>
+ContentBrowserClientQt::GetWebAuthenticationRequestDelegate(
+ content::RenderFrameHost *render_frame_host)
+{
+ return std::make_unique<AuthenticatorRequestClientDelegateQt>(render_frame_host);
+}
+#endif
+
} // namespace QtWebEngineCore
diff --git a/src/core/content_browser_client_qt.h b/src/core/content_browser_client_qt.h
index 87e48db5a..e93a3f117 100644
--- a/src/core/content_browser_client_qt.h
+++ b/src/core/content_browser_client_qt.h
@@ -233,6 +233,12 @@ public:
blink::UserAgentMetadata GetUserAgentMetadata() override { return getUserAgentMetadata(); }
std::string GetProduct() override;
+ content::WebAuthenticationDelegate *GetWebAuthenticationDelegate() override;
+#if !BUILDFLAG(IS_ANDROID)
+ std::unique_ptr<content::AuthenticatorRequestClientDelegate>
+ GetWebAuthenticationRequestDelegate(content::RenderFrameHost *render_frame_host) override;
+#endif
+
private:
BrowserMainPartsQt *m_browserMainParts = nullptr;
};
diff --git a/src/core/doc/src/qwebenginepage_lgpl.qdoc b/src/core/doc/src/qwebenginepage_lgpl.qdoc
index fe84ff76b..93c46e417 100644
--- a/src/core/doc/src/qwebenginepage_lgpl.qdoc
+++ b/src/core/doc/src/qwebenginepage_lgpl.qdoc
@@ -816,3 +816,15 @@
\sa url()
*/
+
+/*!
+ \fn void QWebEnginePage::webAuthUXRequested(QWebEngineWebAuthUXRequest *request);
+ \since 6.7
+
+ This signal is emitted when a WebAuth authenticator needs user interaction
+ during the authentication process. These requests are handled by displaying a dialog to the user.
+
+ The \a request contains the information and API required to complete the WebAuth UX request.
+
+ \sa QWebEngineWebAuthUXRequest
+*/
diff --git a/src/core/web_contents_adapter_client.h b/src/core/web_contents_adapter_client.h
index afbd2b5f2..4a6b1020c 100644
--- a/src/core/web_contents_adapter_client.h
+++ b/src/core/web_contents_adapter_client.h
@@ -36,6 +36,7 @@ QT_FORWARD_DECLARE_CLASS(QWebEngineUrlRequestInterceptor)
QT_FORWARD_DECLARE_CLASS(QWebEngineContextMenuRequest)
QT_FORWARD_DECLARE_CLASS(QWebEngineCertificateError)
QT_FORWARD_DECLARE_CLASS(QWebEngineSettings)
+QT_FORWARD_DECLARE_CLASS(QWebEngineWebAuthUXRequest)
namespace content {
struct DropData;
@@ -215,6 +216,7 @@ public:
virtual ProfileAdapter *profileAdapter() = 0;
virtual WebContentsAdapter* webContentsAdapter() = 0;
virtual void releaseProfile() = 0;
+ virtual void showWebAuthDialog(QWebEngineWebAuthUXRequest *request) = 0;
};
} // namespace QtWebEngineCore
diff --git a/src/webenginequick/api/qquickwebengineforeigntypes_p.h b/src/webenginequick/api/qquickwebengineforeigntypes_p.h
index 1591e596f..7acad61e4 100644
--- a/src/webenginequick/api/qquickwebengineforeigntypes_p.h
+++ b/src/webenginequick/api/qquickwebengineforeigntypes_p.h
@@ -31,6 +31,7 @@
#include <QtWebEngineCore/qwebenginecontextmenurequest.h>
#include <QtWebEngineCore/qwebengineregisterprotocolhandlerrequest.h>
#include <QtWebEngineCore/qwebenginefilesystemaccessrequest.h>
+#include <QtWebEngineCore/qwebenginewebauthuxrequest.h>
QT_BEGIN_NAMESPACE
@@ -198,6 +199,13 @@ namespace ForeginWebEngineFileSystemAccessRequestNamespace
QML_ADDED_IN_VERSION(6, 4)
};
+namespace ForeignWebEngineWebAuthUXRequest {
+ Q_NAMESPACE
+ QML_FOREIGN_NAMESPACE(QWebEngineWebAuthUXRequest)
+ QML_NAMED_ELEMENT(WebEngineWebAuthUXRequest)
+ QML_ADDED_IN_VERSION(6, 7)
+};
+
QT_END_NAMESPACE
#endif // QQUICKWEBENGINEFOREIGNTYPES_H
diff --git a/src/webenginequick/api/qquickwebengineview.cpp b/src/webenginequick/api/qquickwebengineview.cpp
index aafec7295..882b59b6b 100644
--- a/src/webenginequick/api/qquickwebengineview.cpp
+++ b/src/webenginequick/api/qquickwebengineview.cpp
@@ -39,6 +39,7 @@
#include <QtWebEngineCore/qwebenginepage.h>
#include <QtWebEngineCore/qwebengineregisterprotocolhandlerrequest.h>
#include <QtWebEngineCore/qwebenginescriptcollection.h>
+#include <QtWebEngineCore/qwebenginewebauthuxrequest.h>
#include <QtWebEngineCore/private/qwebenginecontextmenurequest_p.h>
#include <QtWebEngineCore/private/qwebenginehistory_p.h>
#include <QtWebEngineCore/private/qwebenginenewwindowrequest_p.h>
@@ -1308,6 +1309,12 @@ void QQuickWebEngineViewPrivate::hideTouchSelectionMenu()
ui()->hideTouchSelectionMenu();
}
+void QQuickWebEngineViewPrivate::showWebAuthDialog(QWebEngineWebAuthUXRequest *request)
+{
+ Q_Q(QQuickWebEngineView);
+ Q_EMIT q->webAuthUXRequested(request);
+}
+
bool QQuickWebEngineView::isLoading() const
{
Q_D(const QQuickWebEngineView);
diff --git a/src/webenginequick/api/qquickwebengineview_p.h b/src/webenginequick/api/qquickwebengineview_p.h
index 7084eb173..63cf2c2f4 100644
--- a/src/webenginequick/api/qquickwebengineview_p.h
+++ b/src/webenginequick/api/qquickwebengineview_p.h
@@ -51,6 +51,7 @@ class QWebEngineNewWindowRequest;
class QWebEngineRegisterProtocolHandlerRequest;
class QQuickWebEngineScriptCollection;
class QQuickWebEngineTouchSelectionMenuRequest;
+class QWebEngineWebAuthUXRequest;
class Q_WEBENGINEQUICK_PRIVATE_EXPORT QQuickWebEngineView : public QQuickItem {
Q_OBJECT
@@ -548,6 +549,7 @@ Q_SIGNALS:
Q_REVISION(6,3) void touchSelectionMenuRequested(QQuickWebEngineTouchSelectionMenuRequest *request);
Q_REVISION(6,4) void touchHandleDelegateChanged();
Q_REVISION(6,4) void fileSystemAccessRequested(const QWebEngineFileSystemAccessRequest &request);
+ Q_REVISION(6, 7) void webAuthUXRequested(QWebEngineWebAuthUXRequest *request);
protected:
void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override;
diff --git a/src/webenginequick/api/qquickwebengineview_p_p.h b/src/webenginequick/api/qquickwebengineview_p_p.h
index aa67fd291..352d4a8c0 100644
--- a/src/webenginequick/api/qquickwebengineview_p_p.h
+++ b/src/webenginequick/api/qquickwebengineview_p_p.h
@@ -132,6 +132,7 @@ public:
void showAutofillPopup(QtWebEngineCore::AutofillPopupController *controller,
const QRect &bounds, bool autoselectFirstSuggestion) override;
void hideAutofillPopup() override;
+ void showWebAuthDialog(QWebEngineWebAuthUXRequest *request) override;
void updateAction(QQuickWebEngineView::WebAction) const;
bool adoptWebContents(QtWebEngineCore::WebContentsAdapter *webContents);
diff --git a/src/webenginequick/doc/src/webengineview_lgpl.qdoc b/src/webenginequick/doc/src/webengineview_lgpl.qdoc
index c4d87dc4d..45b9c3c79 100644
--- a/src/webenginequick/doc/src/webengineview_lgpl.qdoc
+++ b/src/webenginequick/doc/src/webengineview_lgpl.qdoc
@@ -1562,5 +1562,17 @@
\sa QWebEngineDownloadRequest::SavePageFormat
*/
+/*!
+ \qmlsignal WebEngineView::webAuthUXRequested(QWebEngineWebAuthUXRequest *request);
+ \since QtWebEngine 6.7
+
+ This signal is emitted when a WebAuth authenticator requires user interaction
+ during the authentication process. These requests are handled by displaying a dialog to the user.
+
+ The \a request contains the information and API required to complete the WebAuth UX request.
+
+ \sa QWebEngineWebAuthUXRequest
+*/
+
\sa {WebEngine Qt Quick Custom Touch Handle Example}
*/
diff --git a/tests/auto/quick/publicapi/tst_publicapi.cpp b/tests/auto/quick/publicapi/tst_publicapi.cpp
index 2c85b5d25..5f9d262a7 100644
--- a/tests/auto/quick/publicapi/tst_publicapi.cpp
+++ b/tests/auto/quick/publicapi/tst_publicapi.cpp
@@ -23,6 +23,7 @@
#include <QtWebEngineCore/QWebEngineDownloadRequest>
#include <QtWebEngineCore/QWebEngineScript>
#include <QtWebEngineCore/QWebEngineLoadingInfo>
+#include <QtWebEngineCore/QWebEngineWebAuthUXRequest>
#include <private/qquickwebengineview_p.h>
#include <private/qquickwebengineaction_p.h>
#include <private/qquickwebengineclientcertificateselection_p.h>
@@ -70,6 +71,8 @@ static const QList<const QMetaObject *> typesToCheck = QList<const QMetaObject *
<< &QWebEngineQuotaRequest::staticMetaObject
<< &QWebEngineRegisterProtocolHandlerRequest::staticMetaObject
<< &QQuickWebEngineTouchSelectionMenuRequest::staticMetaObject
+ << &QWebEngineWebAuthUXRequest::staticMetaObject
+ << &QWebEngineWebAuthPINRequest::staticMetaObject
;
static QList<QMetaEnum> knownEnumNames = QList<QMetaEnum>()
@@ -813,6 +816,50 @@ static const QStringList expectedAPI = QStringList()
<< "QWebEngineNotification.click() --> void"
<< "QWebEngineNotification.close() --> void"
<< "QWebEngineNotification.closed() --> void"
+ << "QQuickWebEngineView.webAuthUXRequested(QWebEngineWebAuthUXRequest*) --> void"
+ << "QWebEngineWebAuthUXRequest.NotStarted --> WebAuthUXState"
+ << "QWebEngineWebAuthUXRequest.SelectAccount --> WebAuthUXState"
+ << "QWebEngineWebAuthUXRequest.CollectPIN --> WebAuthUXState"
+ << "QWebEngineWebAuthUXRequest.FinishTokenCollection --> WebAuthUXState"
+ << "QWebEngineWebAuthUXRequest.RequestFailed --> WebAuthUXState"
+ << "QWebEngineWebAuthUXRequest.Cancelled --> WebAuthUXState"
+ << "QWebEngineWebAuthUXRequest.Completed --> WebAuthUXState"
+ << "QWebEngineWebAuthUXRequest.PINEntryReason.Set --> PINEntryReason"
+ << "QWebEngineWebAuthUXRequest.PINEntryReason.Change --> PINEntryReason"
+ << "QWebEngineWebAuthUXRequest.PINEntryReason.Challenge --> PINEntryReason"
+ << "QWebEngineWebAuthUXRequest.PINEntryError.NoError --> PINEntryError"
+ << "QWebEngineWebAuthUXRequest.PINEntryError.InternalUvLocked --> PINEntryError"
+ << "QWebEngineWebAuthUXRequest.PINEntryError.WrongPIN --> PINEntryError"
+ << "QWebEngineWebAuthUXRequest.PINEntryError.TooShort --> PINEntryError"
+ << "QWebEngineWebAuthUXRequest.PINEntryError.InvalidCharacters --> PINEntryError"
+ << "QWebEngineWebAuthUXRequest.PINEntryError.SameAsCurrentPIN --> PINEntryError"
+ << "QWebEngineWebAuthUXRequest.RequestFailureReason.Timeout --> RequestFailureReason"
+ << "QWebEngineWebAuthUXRequest.RequestFailureReason.KeyNotRegistered --> RequestFailureReason"
+ << "QWebEngineWebAuthUXRequest.RequestFailureReason.KeyAlreadyRegistered --> RequestFailureReason"
+ << "QWebEngineWebAuthUXRequest.RequestFailureReason.SoftPINBlock --> RequestFailureReason"
+ << "QWebEngineWebAuthUXRequest.RequestFailureReason.HardPINBlock --> RequestFailureReason"
+ << "QWebEngineWebAuthUXRequest.RequestFailureReason.AuthenticatorRemovedDuringPINEntry --> RequestFailureReason"
+ << "QWebEngineWebAuthUXRequest.RequestFailureReason.AuthenticatorMissingResidentKeys --> RequestFailureReason"
+ << "QWebEngineWebAuthUXRequest.RequestFailureReason.AuthenticatorMissingUserVerification --> RequestFailureReason"
+ << "QWebEngineWebAuthUXRequest.RequestFailureReason.AuthenticatorMissingLargeBlob --> RequestFailureReason"
+ << "QWebEngineWebAuthUXRequest.RequestFailureReason.NoCommonAlgorithms --> RequestFailureReason"
+ << "QWebEngineWebAuthUXRequest.RequestFailureReason.StorageFull --> RequestFailureReason"
+ << "QWebEngineWebAuthUXRequest.RequestFailureReason.UserConsentDenied --> RequestFailureReason"
+ << "QWebEngineWebAuthUXRequest.RequestFailureReason.WinUserCancelled --> RequestFailureReason"
+ << "QWebEngineWebAuthUXRequest.userNames --> QStringList"
+ << "QWebEngineWebAuthUXRequest.state --> QWebEngineWebAuthUXRequest::WebAuthUXState"
+ << "QWebEngineWebAuthUXRequest.relyingPartyId --> QString"
+ << "QWebEngineWebAuthUXRequest.pinRequest --> QWebEngineWebAuthPINRequest"
+ << "QWebEngineWebAuthUXRequest.requestFailureReason --> QWebEngineWebAuthUXRequest::RequestFailureReason"
+ << "QWebEngineWebAuthUXRequest.stateChanged(QWebEngineWebAuthUXRequest::WebAuthUXState) --> void"
+ << "QWebEngineWebAuthUXRequest.cancel() --> void"
+ << "QWebEngineWebAuthUXRequest.retry() --> void"
+ << "QWebEngineWebAuthUXRequest.setSelectedAccount(QString) --> void"
+ << "QWebEngineWebAuthUXRequest.setPin(QString) --> void"
+ << "QWebEngineWebAuthPINRequest.reason --> QWebEngineWebAuthUXRequest::PINEntryReason"
+ << "QWebEngineWebAuthPINRequest.error --> QWebEngineWebAuthUXRequest::PINEntryError"
+ << "QWebEngineWebAuthPINRequest.minPinLength --> int"
+ << "QWebEngineWebAuthPINRequest.remainingAttempts --> int"
;
static bool isCheckedEnum(QMetaType t)