From c870baf1bc609bec0d8cfd6ad7230bd66bef4c5b Mon Sep 17 00:00:00 2001 From: Chad Retz Date: Mon, 2 Oct 2017 12:07:52 -0500 Subject: [PATCH] HTTP basic auth support. Fixes #26 --- src/browser_widget.cc | 3 +- src/cef/cef_handler.cc | 15 +++++++++ src/cef/cef_handler.h | 15 +++++++++ src/cef/cef_widget.cc | 53 ++++++++++++++++++++++++++++++++ src/cef/cef_widget.h | 3 ++ src/tests/integration/harness.js | 24 +++++++++++++++ 6 files changed, 112 insertions(+), 1 deletion(-) diff --git a/src/browser_widget.cc b/src/browser_widget.cc index 11dc624..665fa63 100644 --- a/src/browser_widget.cc +++ b/src/browser_widget.cc @@ -395,7 +395,8 @@ void BrowserWidget::RecreateCefWidget(const QString& url, // as much about the transition type. // TODO(cretz): Test when title/favicon is not changed or not present if (!is_loading) { - PageIndex::MarkVisit(CurrentUrl(), current_title_, + auto title = current_title_.isNull() ? "" : current_title_; + PageIndex::MarkVisit(CurrentUrl(), title, current_favicon_url_, current_favicon_); } diff --git a/src/cef/cef_handler.cc b/src/cef/cef_handler.cc index 138e03b..f401808 100644 --- a/src/cef/cef_handler.cc +++ b/src/cef/cef_handler.cc @@ -248,4 +248,19 @@ bool CefHandler::OnOpenURLFromTab( return true; } +bool CefHandler::GetAuthCredentials(CefRefPtr browser, + CefRefPtr frame, + bool is_proxy, + const CefString& host, + int port, + const CefString& realm, + const CefString& scheme, + CefRefPtr callback) { + emit AuthRequest(frame, is_proxy, + QString::fromStdString(host.ToString()), port, + QString::fromStdString(realm.ToString()), + QString::fromStdString(scheme.ToString()), callback); + return true; +} + } // namespace doogie diff --git a/src/cef/cef_handler.h b/src/cef/cef_handler.h index 75c75b8..9944da7 100644 --- a/src/cef/cef_handler.h +++ b/src/cef/cef_handler.h @@ -235,6 +235,14 @@ class CefHandler : const CefString& target_url, CefRequestHandler::WindowOpenDisposition target_disposition, bool user_gesture) override; + bool GetAuthCredentials(CefRefPtr browser, + CefRefPtr frame, + bool is_proxy, + const CefString& host, + int port, + const CefString& realm, + const CefString& scheme, + CefRefPtr callback) override; signals: void PreContextMenu(CefRefPtr params, @@ -279,6 +287,13 @@ class CefHandler : CefRefPtr ssl_info, CefRefPtr callback); void PageOpen(WindowOpenType type, const QString& url, bool user_gesture); + void AuthRequest(CefRefPtr frame, + bool is_proxy, + const QString& host, + int port, + const QString& realm, + const QString& scheme, + CefRefPtr callback); void BrowserLog(const QString& str); private: diff --git a/src/cef/cef_widget.cc b/src/cef/cef_widget.cc index f9e9c59..b07a1f7 100644 --- a/src/cef/cef_widget.cc +++ b/src/cef/cef_widget.cc @@ -67,6 +67,12 @@ CefWidget::CefWidget(const Cef& cef, }); connect(handler_, &CefHandler::ShowBeforeUnloadDialog, this, &CefWidget::ShowBeforeUnloadDialog); + connect(handler_, &CefHandler::AuthRequest, + [=](CefRefPtr, bool, const QString&, + int, const QString& realm, const QString&, + CefRefPtr callback) { + Util::RunOnMainThread([=]() { AuthRequest(realm, callback); }); + }); InitBrowser(bubble, url); } @@ -353,4 +359,51 @@ void CefWidget::InitBrowser(const Bubble& bubble, const QString& url) { bubble.CreateCefRequestContext()); } +void CefWidget::AuthRequest(const QString& realm, + CefRefPtr callback) const { + // Make a simple dialog to grab creds + auto layout = new QGridLayout; + auto header_label = + new QLabel(QString("Authentication requested for: ") + realm); + header_label->setTextFormat(Qt::PlainText); + layout->addWidget(header_label, 0, 0, 1, 2, Qt::AlignCenter); + layout->addWidget(new QLabel("Username:"), 1, 0); + auto username = new QLineEdit; + layout->addWidget(username, 1, 1); + layout->setColumnStretch(1, 1); + layout->addWidget(new QLabel("Password:"), 2, 0); + auto password = new QLineEdit; + password->setEchoMode(QLineEdit::Password); + layout->addWidget(password, 2, 1); + auto buttons = new QDialogButtonBox; + buttons->addButton(QDialogButtonBox::Ok); + buttons->addButton(QDialogButtonBox::Cancel); + layout->addWidget(buttons, 3, 0, 1, 2, Qt::AlignCenter); + auto dialog = new QDialog(); + dialog->setWindowTitle("Authentication Requested"); + dialog->setLayout(layout); + connect(buttons->button(QDialogButtonBox::Ok), &QPushButton::clicked, + dialog, &QDialog::accept); + connect(buttons->button(QDialogButtonBox::Cancel), &QPushButton::clicked, + dialog, &QDialog::reject); + + if (dialog->exec() == QDialog::Accepted) { + // TODO(cretz): downstream issue: + // https://bitbucket.org/chromiumembedded/cef/issues/2275 + if (username->text().isEmpty()) { + QMessageBox::critical(dialog, "Empty Username", + "Empty usernames are not currently supported"); + // Defer a retry + QTimer::singleShot(0, [=]() { AuthRequest(realm, callback); }); + return; + } + callback->Continue(CefString(username->text().toStdString()), + CefString(password->text().toStdString())); + } else { + callback->Cancel(); + } + + dialog->deleteLater(); +} + } // namespace doogie diff --git a/src/cef/cef_widget.h b/src/cef/cef_widget.h index 946cc24..acd541e 100644 --- a/src/cef/cef_widget.h +++ b/src/cef/cef_widget.h @@ -148,6 +148,9 @@ class CefWidget : public CefBaseWidget { void InitBrowser(const Bubble& bubble, const QString& url); + void AuthRequest(const QString& realm, + CefRefPtr callback) const; + CefRefPtr handler_; CefRefPtr browser_; CefRefPtr dev_tools_handler_; diff --git a/src/tests/integration/harness.js b/src/tests/integration/harness.js index 33cfadb..50ae894 100644 --- a/src/tests/integration/harness.js +++ b/src/tests/integration/harness.js @@ -33,9 +33,33 @@ exports.Harness = class Harness { return new Promise((resolve, reject) => { const resServe = http.createServer((req, res) => { const reqUrl = url.parse(req.url, true) + + // We'll ask for auth or check auth ("user"/"pass") if requested + if (reqUrl.query['basicAuth']) { + let success = false + if (req.headers['authorization']) { + const pieces = req.headers['authorization'].split(' ', 2) + if (pieces.length == 2) { + const creds = (new Buffer(pieces[1], 'base64')).toString().split(':', 2) + console.log('Creds:', creds) + success = creds.length == 2 && creds[0] === 'user' && creds[1] === 'pass' + } + } + // No auth head means ask and leave + if (!success) { + res.statusCode = 401 + res.setHeader('WWW-Authenticate', 'Basic realm="Type \'user\' and \'pass\'"') + res.end('HTTP basic auth requested') + return + } + } + res.writeHead(200) + // We'll just return if it wants us to hang forever if (reqUrl.query['loadForever']) return + + // Read what was asked for const stream = fs.createReadStream( path.join(this.resourceDir, path.normalize(reqUrl.pathname))) stream.once('error', e => {