mirror of
https://github.com/nextcloud/desktop.git
synced 2024-10-24 05:15:40 +03:00
Login Flow V2: adds re-auth upon logout, improvements
- Implements re-auth upon logout -> login - Improves UI and security TODO: - SSL: Client certificate login is possible at the first time only but missing after relaunch Signed-off-by: Michael Schuster <michael@schuster.ms>
This commit is contained in:
parent
7add98e9a3
commit
fd8345ccbe
11 changed files with 376 additions and 47 deletions
|
@ -39,6 +39,7 @@ set(client_UI_SRCS
|
|||
wizard/owncloudhttpcredspage.ui
|
||||
wizard/owncloudoauthcredspage.ui
|
||||
wizard/flow2authcredspage.ui
|
||||
wizard/flow2authwidget.ui
|
||||
wizard/owncloudsetupnocredspage.ui
|
||||
wizard/owncloudwizardresultpage.ui
|
||||
wizard/webview.ui
|
||||
|
@ -114,6 +115,7 @@ set(client_SRCS
|
|||
wizard/owncloudhttpcredspage.cpp
|
||||
wizard/owncloudoauthcredspage.cpp
|
||||
wizard/flow2authcredspage.cpp
|
||||
wizard/flow2authwidget.cpp
|
||||
wizard/owncloudsetuppage.cpp
|
||||
wizard/owncloudwizardcommon.cpp
|
||||
wizard/owncloudwizard.cpp
|
||||
|
|
|
@ -34,9 +34,9 @@ Flow2Auth::~Flow2Auth()
|
|||
|
||||
void Flow2Auth::start()
|
||||
{
|
||||
// Note: All startup code is in openBrowser() to allow reinitiate a new request with
|
||||
// fresh tokens. Opening the same pollEndpoint link twice triggers an expiration
|
||||
// message by the server (security, intended design).
|
||||
// Note: All startup code is in openBrowser() to allow reinitiate a new request with
|
||||
// fresh tokens. Opening the same pollEndpoint link twice triggers an expiration
|
||||
// message by the server (security, intended design).
|
||||
openBrowser();
|
||||
}
|
||||
|
||||
|
@ -91,6 +91,7 @@ void Flow2Auth::openBrowser()
|
|||
_pollEndpoint = pollEndpoint;
|
||||
|
||||
|
||||
// Start polling
|
||||
ConfigFile cfg;
|
||||
std::chrono::milliseconds polltime = cfg.remotePollInterval();
|
||||
qCInfo(lcFlow2auth) << "setting remote poll timer interval to" << polltime.count() << "msec";
|
||||
|
@ -99,10 +100,10 @@ void Flow2Auth::openBrowser()
|
|||
_pollTimer.start();
|
||||
|
||||
|
||||
if (!QDesktopServices::openUrl(authorisationLink())) {
|
||||
// TODO: Ask the user to copy and open the link instead of failing here!
|
||||
|
||||
// We cannot open the browser, then we claim we don't support OAuth.
|
||||
// Try to open Browser
|
||||
if (!QDesktopServices::openUrl(authorisationLink())) {
|
||||
// We cannot open the browser, then we claim we don't support Flow2Auth.
|
||||
// Our UI callee should ask the user to copy and open the link.
|
||||
emit result(NotSupported, QString());
|
||||
}
|
||||
});
|
||||
|
@ -110,7 +111,7 @@ void Flow2Auth::openBrowser()
|
|||
|
||||
void Flow2Auth::slotPollTimerTimeout()
|
||||
{
|
||||
_pollTimer.stop();
|
||||
_pollTimer.stop();
|
||||
|
||||
// Step 2: Poll
|
||||
QNetworkRequest req;
|
||||
|
@ -122,6 +123,7 @@ void Flow2Auth::slotPollTimerTimeout()
|
|||
|
||||
auto job = _account->sendRequest("POST", _pollEndpoint, req, requestBody);
|
||||
job->setTimeout(qMin(30 * 1000ll, job->timeoutMsec()));
|
||||
|
||||
QObject::connect(job, &SimpleNetworkJob::finishedSignal, this, [this](QNetworkReply *reply) {
|
||||
auto jsonData = reply->readAll();
|
||||
QJsonParseError jsonParseError;
|
||||
|
@ -149,15 +151,25 @@ void Flow2Auth::slotPollTimerTimeout()
|
|||
}
|
||||
qCDebug(lcFlow2auth) << "Error when polling for the appPassword" << json << errorReason;
|
||||
|
||||
_pollTimer.start();
|
||||
// Forget sensitive data
|
||||
appPassword.clear();
|
||||
loginName.clear();
|
||||
|
||||
// Failed: poll again
|
||||
_pollTimer.start();
|
||||
return;
|
||||
}
|
||||
|
||||
qCInfo(lcFlow2auth) << "Success getting the appPassword for user: " << loginName << " on server: " << serverUrl;
|
||||
|
||||
_account->setUrl(serverUrl);
|
||||
// Success
|
||||
qCInfo(lcFlow2auth) << "Success getting the appPassword for user: " << loginName << ", server: " << serverUrl.toString();
|
||||
|
||||
emit result(LoggedIn, loginName, appPassword);
|
||||
_account->setUrl(serverUrl);
|
||||
|
||||
emit result(LoggedIn, loginName, appPassword);
|
||||
|
||||
// Forget sensitive data
|
||||
appPassword.clear();
|
||||
loginName.clear();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -49,10 +49,9 @@ public:
|
|||
signals:
|
||||
/**
|
||||
* The state has changed.
|
||||
* when logged in, token has the value of the token.
|
||||
* when logged in, appPassword has the value of the app password.
|
||||
*/
|
||||
// TODO: Remove refreshToken
|
||||
void result(Flow2Auth::Result result, const QString &user = QString(), const QString &token = QString(), const QString &refreshToken = QString());
|
||||
void result(Flow2Auth::Result result, const QString &user = QString(), const QString &appPassword = QString());
|
||||
|
||||
private slots:
|
||||
void slotPollTimerTimeout();
|
||||
|
|
|
@ -114,12 +114,17 @@ void WebFlowCredentials::fetchFromKeychain() {
|
|||
}
|
||||
|
||||
void WebFlowCredentials::askFromUser() {
|
||||
_askDialog = new WebFlowCredentialsDialog();
|
||||
// LoginFlowV2 > WebViewFlow > OAuth > Shib > Basic
|
||||
bool useFlow2 = (_account->serverVersionInt() >= Account::makeServerVersion(16, 0, 0));
|
||||
|
||||
QUrl url = _account->url();
|
||||
QString path = url.path() + "/index.php/login/flow";
|
||||
url.setPath(path);
|
||||
_askDialog->setUrl(url);
|
||||
_askDialog = new WebFlowCredentialsDialog(_account, useFlow2);
|
||||
|
||||
if (!useFlow2) {
|
||||
QUrl url = _account->url();
|
||||
QString path = url.path() + "/index.php/login/flow";
|
||||
url.setPath(path);
|
||||
_askDialog->setUrl(url);
|
||||
}
|
||||
|
||||
QString msg = tr("You have been logged out of %1 as user %2. Please login again")
|
||||
.arg(_account->displayName(), _user);
|
||||
|
@ -142,10 +147,12 @@ void WebFlowCredentials::slotAskFromUserCredentialsProvided(const QString &user,
|
|||
.arg(_user);
|
||||
_askDialog->setError(msg);
|
||||
|
||||
QUrl url = _account->url();
|
||||
QString path = url.path() + "/index.php/login/flow";
|
||||
url.setPath(path);
|
||||
_askDialog->setUrl(url);
|
||||
if (!_askDialog->isUsingFlow2()) {
|
||||
QUrl url = _account->url();
|
||||
QString path = url.path() + "/index.php/login/flow";
|
||||
url.setPath(path);
|
||||
_askDialog->setUrl(url);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -3,13 +3,21 @@
|
|||
#include <QVBoxLayout>
|
||||
#include <QLabel>
|
||||
|
||||
#include "theme.h"
|
||||
#include "wizard/owncloudwizardcommon.h"
|
||||
#include "wizard/webview.h"
|
||||
#include "wizard/flow2authwidget.h"
|
||||
|
||||
namespace OCC {
|
||||
|
||||
WebFlowCredentialsDialog::WebFlowCredentialsDialog(QWidget *parent)
|
||||
: QDialog(parent)
|
||||
WebFlowCredentialsDialog::WebFlowCredentialsDialog(Account *account, bool useFlow2, QWidget *parent)
|
||||
: QDialog(parent),
|
||||
_useFlow2(useFlow2),
|
||||
_flow2AuthWidget(nullptr),
|
||||
_webView(nullptr)
|
||||
{
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
_layout = new QVBoxLayout(this);
|
||||
|
||||
//QString msg = tr("You have been logged out of %1 as user %2, please login again")
|
||||
|
@ -17,28 +25,44 @@ WebFlowCredentialsDialog::WebFlowCredentialsDialog(QWidget *parent)
|
|||
_infoLabel = new QLabel();
|
||||
_layout->addWidget(_infoLabel);
|
||||
|
||||
_webView = new WebView();
|
||||
_layout->addWidget(_webView);
|
||||
if (_useFlow2) {
|
||||
_flow2AuthWidget = new Flow2AuthWidget(account);
|
||||
_layout->addWidget(_flow2AuthWidget);
|
||||
|
||||
connect(_flow2AuthWidget, &Flow2AuthWidget::urlCatched, this, &WebFlowCredentialsDialog::urlCatched);
|
||||
} else {
|
||||
_webView = new WebView();
|
||||
_layout->addWidget(_webView);
|
||||
|
||||
connect(_webView, &WebView::urlCatched, this, &WebFlowCredentialsDialog::urlCatched);
|
||||
}
|
||||
|
||||
_errorLabel = new QLabel();
|
||||
_errorLabel->hide();
|
||||
_layout->addWidget(_errorLabel);
|
||||
|
||||
setLayout(_layout);
|
||||
Theme *theme = Theme::instance();
|
||||
WizardCommon::initErrorLabel(_errorLabel);
|
||||
|
||||
connect(_webView, &WebView::urlCatched, this, &WebFlowCredentialsDialog::urlCatched);
|
||||
setLayout(_layout);
|
||||
}
|
||||
|
||||
void WebFlowCredentialsDialog::closeEvent(QCloseEvent* e) {
|
||||
Q_UNUSED(e);
|
||||
|
||||
// Force calling WebView::~WebView() earlier so that _profile and _page are
|
||||
// deleted in the correct order.
|
||||
delete _webView;
|
||||
if (_webView) {
|
||||
// Force calling WebView::~WebView() earlier so that _profile and _page are
|
||||
// deleted in the correct order.
|
||||
delete _webView;
|
||||
}
|
||||
|
||||
if (_flow2AuthWidget)
|
||||
delete _flow2AuthWidget;
|
||||
}
|
||||
|
||||
void WebFlowCredentialsDialog::setUrl(const QUrl &url) {
|
||||
_webView->setUrl(url);
|
||||
if (_webView)
|
||||
_webView->setUrl(url);
|
||||
}
|
||||
|
||||
void WebFlowCredentialsDialog::setInfo(const QString &msg) {
|
||||
|
@ -46,6 +70,11 @@ void WebFlowCredentialsDialog::setInfo(const QString &msg) {
|
|||
}
|
||||
|
||||
void WebFlowCredentialsDialog::setError(const QString &error) {
|
||||
if (_useFlow2 && _flow2AuthWidget) {
|
||||
_flow2AuthWidget->setError(error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (error.isEmpty()) {
|
||||
_errorLabel->hide();
|
||||
} else {
|
||||
|
|
|
@ -4,23 +4,30 @@
|
|||
#include <QDialog>
|
||||
#include <QUrl>
|
||||
|
||||
#include "accountfwd.h"
|
||||
|
||||
class QLabel;
|
||||
class QVBoxLayout;
|
||||
|
||||
namespace OCC {
|
||||
|
||||
class WebView;
|
||||
class Flow2AuthWidget;
|
||||
|
||||
class WebFlowCredentialsDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
WebFlowCredentialsDialog(QWidget *parent = nullptr);
|
||||
WebFlowCredentialsDialog(Account *account, bool useFlow2, QWidget *parent = nullptr);
|
||||
|
||||
void setUrl(const QUrl &url);
|
||||
void setInfo(const QString &msg);
|
||||
void setError(const QString &error);
|
||||
|
||||
const bool isUsingFlow2() const {
|
||||
return _useFlow2;
|
||||
}
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent * e) override;
|
||||
|
||||
|
@ -28,7 +35,11 @@ signals:
|
|||
void urlCatched(const QString user, const QString pass, const QString host);
|
||||
|
||||
private:
|
||||
bool _useFlow2;
|
||||
|
||||
Flow2AuthWidget *_flow2AuthWidget;
|
||||
WebView *_webView;
|
||||
|
||||
QLabel *_errorLabel;
|
||||
QLabel *_infoLabel;
|
||||
QVBoxLayout *_layout;
|
||||
|
|
|
@ -81,17 +81,25 @@ void OCC::Flow2AuthCredsPage::cleanupPage()
|
|||
// The next or back button was activated, show the wizard again
|
||||
wizard()->show();
|
||||
_asyncAuth.reset();
|
||||
|
||||
// Forget sensitive data
|
||||
_appPassword.clear();
|
||||
_user.clear();
|
||||
}
|
||||
|
||||
void Flow2AuthCredsPage::asyncAuthResult(Flow2Auth::Result r, const QString &user,
|
||||
const QString &token, const QString &refreshToken)
|
||||
const QString &appPassword)
|
||||
{
|
||||
switch (r) {
|
||||
case Flow2Auth::NotSupported: {
|
||||
/* Flow2Auth not supported (can't open browser), fallback to HTTP credentials */
|
||||
OwncloudWizard *ocWizard = qobject_cast<OwncloudWizard *>(wizard());
|
||||
/* Flow2Auth not supported (can't open browser) */
|
||||
_ui.errorLabel->setText(tr("Unable to open the Browser, please copy the link to your Browser."));
|
||||
_ui.errorLabel->show();
|
||||
|
||||
/* Don't fallback to HTTP credentials */
|
||||
/*OwncloudWizard *ocWizard = qobject_cast<OwncloudWizard *>(wizard());
|
||||
ocWizard->back();
|
||||
ocWizard->setAuthType(DetermineAuthTypeJob::Basic);
|
||||
ocWizard->setAuthType(DetermineAuthTypeJob::Basic);*/
|
||||
break;
|
||||
}
|
||||
case Flow2Auth::Error:
|
||||
|
@ -100,9 +108,8 @@ void Flow2AuthCredsPage::asyncAuthResult(Flow2Auth::Result r, const QString &use
|
|||
wizard()->show();
|
||||
break;
|
||||
case Flow2Auth::LoggedIn: {
|
||||
_token = token;
|
||||
_user = user;
|
||||
_refreshToken = refreshToken;
|
||||
_appPassword = appPassword;
|
||||
OwncloudWizard *ocWizard = qobject_cast<OwncloudWizard *>(wizard());
|
||||
Q_ASSERT(ocWizard);
|
||||
emit connectToOCUrl(ocWizard->account()->url().toString());
|
||||
|
@ -125,7 +132,7 @@ AbstractCredentials *Flow2AuthCredsPage::getCredentials() const
|
|||
{
|
||||
OwncloudWizard *ocWizard = qobject_cast<OwncloudWizard *>(wizard());
|
||||
Q_ASSERT(ocWizard);
|
||||
return new HttpCredentialsGui(_user, _token, _refreshToken,
|
||||
return new HttpCredentialsGui(_user, _appPassword, QString(),
|
||||
ocWizard->_clientSslCertificate, ocWizard->_clientSslKey);
|
||||
}
|
||||
|
||||
|
|
|
@ -46,16 +46,14 @@ public:
|
|||
bool isComplete() const override;
|
||||
|
||||
public Q_SLOTS:
|
||||
void asyncAuthResult(Flow2Auth::Result, const QString &user, const QString &token,
|
||||
const QString &reniewToken);
|
||||
void asyncAuthResult(Flow2Auth::Result, const QString &user, const QString &appPassword);
|
||||
|
||||
signals:
|
||||
void connectToOCUrl(const QString &);
|
||||
|
||||
public:
|
||||
QString _user;
|
||||
QString _token;
|
||||
QString _refreshToken;
|
||||
QString _appPassword;
|
||||
QScopedPointer<Flow2Auth> _asyncAuth;
|
||||
Ui_Flow2AuthCredsPage _ui;
|
||||
};
|
||||
|
|
113
src/gui/wizard/flow2authwidget.cpp
Normal file
113
src/gui/wizard/flow2authwidget.cpp
Normal file
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* Copyright (C) by Michael Schuster <michael@nextcloud.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
#include "flow2authwidget.h"
|
||||
|
||||
#include <QDesktopServices>
|
||||
#include <QProgressBar>
|
||||
#include <QLoggingCategory>
|
||||
#include <QLocale>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include <QMenu>
|
||||
#include <QClipboard>
|
||||
|
||||
#include "common/utility.h"
|
||||
#include "account.h"
|
||||
#include "theme.h"
|
||||
#include "wizard/owncloudwizardcommon.h"
|
||||
|
||||
namespace OCC {
|
||||
|
||||
Q_LOGGING_CATEGORY(lcFlow2AuthWidget, "gui.wizard.flow2authwidget", QtInfoMsg)
|
||||
|
||||
|
||||
Flow2AuthWidget::Flow2AuthWidget(Account *account, QWidget *parent)
|
||||
: QWidget(parent),
|
||||
_account(account),
|
||||
_ui()
|
||||
{
|
||||
_ui.setupUi(this);
|
||||
|
||||
Theme *theme = Theme::instance();
|
||||
_ui.topLabel->hide();
|
||||
_ui.bottomLabel->hide();
|
||||
QVariant variant = theme->customMedia(Theme::oCSetupTop);
|
||||
WizardCommon::setupCustomMedia(variant, _ui.topLabel);
|
||||
variant = theme->customMedia(Theme::oCSetupBottom);
|
||||
WizardCommon::setupCustomMedia(variant, _ui.bottomLabel);
|
||||
|
||||
WizardCommon::initErrorLabel(_ui.errorLabel);
|
||||
|
||||
connect(_ui.openLinkButton, &QCommandLinkButton::clicked, [this] {
|
||||
_ui.errorLabel->hide();
|
||||
if (_asyncAuth)
|
||||
_asyncAuth->openBrowser();
|
||||
});
|
||||
_ui.openLinkButton->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
QObject::connect(_ui.openLinkButton, &QWidget::customContextMenuRequested, [this](const QPoint &pos) {
|
||||
auto menu = new QMenu(_ui.openLinkButton);
|
||||
menu->addAction(tr("Copy link to clipboard"), this, [this] {
|
||||
if (_asyncAuth)
|
||||
QApplication::clipboard()->setText(_asyncAuth->authorisationLink().toString(QUrl::FullyEncoded));
|
||||
});
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
menu->popup(_ui.openLinkButton->mapToGlobal(pos));
|
||||
});
|
||||
|
||||
_asyncAuth.reset(new Flow2Auth(_account, this));
|
||||
connect(_asyncAuth.data(), &Flow2Auth::result, this, &Flow2AuthWidget::asyncAuthResult, Qt::QueuedConnection);
|
||||
_asyncAuth->start();
|
||||
}
|
||||
|
||||
void Flow2AuthWidget::asyncAuthResult(Flow2Auth::Result r, const QString &user,
|
||||
const QString &appPassword)
|
||||
{
|
||||
switch (r) {
|
||||
case Flow2Auth::NotSupported:
|
||||
/* Flow2Auth can't open browser */
|
||||
_ui.errorLabel->setText(tr("Unable to open the Browser, please copy the link to your Browser."));
|
||||
_ui.errorLabel->show();
|
||||
break;
|
||||
case Flow2Auth::Error:
|
||||
/* Error while getting the access token. (Timeout, or the server did not accept our client credentials */
|
||||
_ui.errorLabel->show();
|
||||
break;
|
||||
case Flow2Auth::LoggedIn: {
|
||||
_user = user;
|
||||
_appPassword = appPassword;
|
||||
emit urlCatched(_user, _appPassword, QString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Flow2AuthWidget::setError(const QString &error) {
|
||||
if (error.isEmpty()) {
|
||||
_ui.errorLabel->hide();
|
||||
} else {
|
||||
_ui.errorLabel->setText(error);
|
||||
_ui.errorLabel->show();
|
||||
}
|
||||
}
|
||||
|
||||
Flow2AuthWidget::~Flow2AuthWidget() {
|
||||
_asyncAuth.reset();
|
||||
|
||||
// Forget sensitive data
|
||||
_appPassword.clear();
|
||||
_user.clear();
|
||||
}
|
||||
|
||||
}
|
52
src/gui/wizard/flow2authwidget.h
Normal file
52
src/gui/wizard/flow2authwidget.h
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (C) by Michael Schuster <michael@nextcloud.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
#ifndef FLOW2AUTHWIDGET_H
|
||||
#define FLOW2AUTHWIDGET_H
|
||||
|
||||
#include <QUrl>
|
||||
#include <QWidget>
|
||||
|
||||
#include "creds/flow2auth.h"
|
||||
|
||||
#include "ui_flow2authwidget.h"
|
||||
|
||||
namespace OCC {
|
||||
|
||||
class Flow2AuthWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Flow2AuthWidget(Account *account, QWidget *parent = nullptr);
|
||||
virtual ~Flow2AuthWidget();
|
||||
|
||||
void setError(const QString &error);
|
||||
|
||||
public Q_SLOTS:
|
||||
void asyncAuthResult(Flow2Auth::Result, const QString &user, const QString &appPassword);
|
||||
|
||||
signals:
|
||||
void urlCatched(const QString user, const QString pass, const QString host);
|
||||
|
||||
private:
|
||||
Account *_account;
|
||||
QString _user;
|
||||
QString _appPassword;
|
||||
QScopedPointer<Flow2Auth> _asyncAuth;
|
||||
Ui_Flow2AuthWidget _ui;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FLOW2AUTHWIDGET_H
|
99
src/gui/wizard/flow2authwidget.ui
Normal file
99
src/gui/wizard/flow2authwidget.ui
Normal file
|
@ -0,0 +1,99 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Flow2AuthWidget</class>
|
||||
<widget class="QWidget" name="Flow2AuthWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>500</width>
|
||||
<height>280</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>500</width>
|
||||
<height>280</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="topLabel">
|
||||
<property name="text">
|
||||
<string notr="true">TextLabel</string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Please switch to your browser to proceed.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="errorLabel">
|
||||
<property name="text">
|
||||
<string>An error occurred while connecting. Please try again.</string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::PlainText</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCommandLinkButton" name="openLinkButton">
|
||||
<property name="text">
|
||||
<string>Re-open Browser (or right-click to copy link)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>127</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="bottomLabel">
|
||||
<property name="text">
|
||||
<string notr="true">TextLabel</string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
Loading…
Reference in a new issue