fix: implement 'login' event for WebContents (#20954)
This commit is contained in:
parent
049bd09150
commit
034f4d5734
16 changed files with 239 additions and 247 deletions
|
@ -314,10 +314,8 @@ Returns:
|
||||||
|
|
||||||
* `event` Event
|
* `event` Event
|
||||||
* `webContents` [WebContents](web-contents.md)
|
* `webContents` [WebContents](web-contents.md)
|
||||||
* `request` Object
|
* `authenticationResponseDetails` Object
|
||||||
* `method` String
|
|
||||||
* `url` URL
|
* `url` URL
|
||||||
* `referrer` URL
|
|
||||||
* `authInfo` Object
|
* `authInfo` Object
|
||||||
* `isProxy` Boolean
|
* `isProxy` Boolean
|
||||||
* `scheme` String
|
* `scheme` String
|
||||||
|
@ -337,7 +335,7 @@ should prevent the default behavior with `event.preventDefault()` and call
|
||||||
```javascript
|
```javascript
|
||||||
const { app } = require('electron')
|
const { app } = require('electron')
|
||||||
|
|
||||||
app.on('login', (event, webContents, request, authInfo, callback) => {
|
app.on('login', (event, webContents, details, authInfo, callback) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
callback('username', 'secret')
|
callback('username', 'secret')
|
||||||
})
|
})
|
||||||
|
|
|
@ -454,10 +454,8 @@ The usage is the same with [the `select-client-certificate` event of
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
* `event` Event
|
* `event` Event
|
||||||
* `request` Object
|
* `authenticationResponseDetails` Object
|
||||||
* `method` String
|
|
||||||
* `url` URL
|
* `url` URL
|
||||||
* `referrer` URL
|
|
||||||
* `authInfo` Object
|
* `authInfo` Object
|
||||||
* `isProxy` Boolean
|
* `isProxy` Boolean
|
||||||
* `scheme` String
|
* `scheme` String
|
||||||
|
|
|
@ -105,9 +105,9 @@ if (process.platform === 'linux') {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Routes the events to webContents.
|
// Routes the events to webContents.
|
||||||
const events = ['login', 'certificate-error', 'select-client-certificate']
|
const events = ['certificate-error', 'select-client-certificate']
|
||||||
for (const name of events) {
|
for (const name of events) {
|
||||||
app.on(name as 'login', (event, webContents, ...args: any[]) => {
|
app.on(name as 'certificate-error', (event, webContents, ...args: any[]) => {
|
||||||
webContents.emit(name, event, ...args)
|
webContents.emit(name, event, ...args)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -423,6 +423,10 @@ WebContents.prototype._init = function () {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.on('login', (event, ...args) => {
|
||||||
|
app.emit('login', event, this, ...args)
|
||||||
|
})
|
||||||
|
|
||||||
const event = process.electronBinding('event').createEmpty()
|
const event = process.electronBinding('event').createEmpty()
|
||||||
app.emit('web-contents-created', event, this)
|
app.emit('web-contents-created', event, this)
|
||||||
}
|
}
|
||||||
|
|
|
@ -495,15 +495,6 @@ void OnClientCertificateSelected(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PassLoginInformation(scoped_refptr<LoginHandler> login_handler,
|
|
||||||
gin_helper::Arguments* args) {
|
|
||||||
base::string16 username, password;
|
|
||||||
if (args->GetNext(&username) && args->GetNext(&password))
|
|
||||||
login_handler->Login(username, password);
|
|
||||||
else
|
|
||||||
login_handler->CancelAuth();
|
|
||||||
}
|
|
||||||
|
|
||||||
#if defined(USE_NSS_CERTS)
|
#if defined(USE_NSS_CERTS)
|
||||||
int ImportIntoCertStore(CertificateManagerModel* model,
|
int ImportIntoCertStore(CertificateManagerModel* model,
|
||||||
const base::DictionaryValue& options) {
|
const base::DictionaryValue& options) {
|
||||||
|
@ -667,25 +658,6 @@ void App::OnNewWindowForTab() {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void App::OnLogin(scoped_refptr<LoginHandler> login_handler,
|
|
||||||
const base::DictionaryValue& request_details) {
|
|
||||||
v8::Locker locker(isolate());
|
|
||||||
v8::HandleScope handle_scope(isolate());
|
|
||||||
bool prevent_default = false;
|
|
||||||
content::WebContents* web_contents = login_handler->GetWebContents();
|
|
||||||
if (web_contents) {
|
|
||||||
prevent_default =
|
|
||||||
Emit("login", WebContents::FromOrCreate(isolate(), web_contents),
|
|
||||||
request_details, *login_handler->auth_info(),
|
|
||||||
base::BindOnce(&PassLoginInformation,
|
|
||||||
base::RetainedRef(login_handler)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default behavior is to always cancel the auth.
|
|
||||||
if (!prevent_default)
|
|
||||||
login_handler->CancelAuth();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool App::CanCreateWindow(
|
bool App::CanCreateWindow(
|
||||||
content::RenderFrameHost* opener,
|
content::RenderFrameHost* opener,
|
||||||
const GURL& opener_url,
|
const GURL& opener_url,
|
||||||
|
|
|
@ -86,8 +86,6 @@ class App : public AtomBrowserClient::Delegate,
|
||||||
void OnActivate(bool has_visible_windows) override;
|
void OnActivate(bool has_visible_windows) override;
|
||||||
void OnWillFinishLaunching() override;
|
void OnWillFinishLaunching() override;
|
||||||
void OnFinishLaunching(const base::DictionaryValue& launch_info) override;
|
void OnFinishLaunching(const base::DictionaryValue& launch_info) override;
|
||||||
void OnLogin(scoped_refptr<LoginHandler> login_handler,
|
|
||||||
const base::DictionaryValue& request_details) override;
|
|
||||||
void OnAccessibilitySupportChanged() override;
|
void OnAccessibilitySupportChanged() override;
|
||||||
void OnPreMainMessageLoopRun() override;
|
void OnPreMainMessageLoopRun() override;
|
||||||
#if defined(OS_MACOSX)
|
#if defined(OS_MACOSX)
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
#include "content/public/browser/browser_ppapi_host.h"
|
#include "content/public/browser/browser_ppapi_host.h"
|
||||||
#include "content/public/browser/browser_task_traits.h"
|
#include "content/public/browser/browser_task_traits.h"
|
||||||
#include "content/public/browser/client_certificate_delegate.h"
|
#include "content/public/browser/client_certificate_delegate.h"
|
||||||
|
#include "content/public/browser/login_delegate.h"
|
||||||
#include "content/public/browser/overlay_window.h"
|
#include "content/public/browser/overlay_window.h"
|
||||||
#include "content/public/browser/render_frame_host.h"
|
#include "content/public/browser/render_frame_host.h"
|
||||||
#include "content/public/browser/render_process_host.h"
|
#include "content/public/browser/render_process_host.h"
|
||||||
|
@ -1104,4 +1105,18 @@ void AtomBrowserClient::BindHostReceiverForRenderer(
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<content::LoginDelegate> AtomBrowserClient::CreateLoginDelegate(
|
||||||
|
const net::AuthChallengeInfo& auth_info,
|
||||||
|
content::WebContents* web_contents,
|
||||||
|
const content::GlobalRequestID& request_id,
|
||||||
|
bool is_main_frame,
|
||||||
|
const GURL& url,
|
||||||
|
scoped_refptr<net::HttpResponseHeaders> response_headers,
|
||||||
|
bool first_auth_attempt,
|
||||||
|
LoginAuthRequiredCallback auth_required_callback) {
|
||||||
|
return std::make_unique<LoginHandler>(
|
||||||
|
auth_info, web_contents, is_main_frame, url, response_headers,
|
||||||
|
first_auth_attempt, std::move(auth_required_callback));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace electron
|
} // namespace electron
|
||||||
|
|
|
@ -211,6 +211,15 @@ class AtomBrowserClient : public content::ContentBrowserClient,
|
||||||
const base::Optional<url::Origin>& initiating_origin,
|
const base::Optional<url::Origin>& initiating_origin,
|
||||||
mojo::PendingRemote<network::mojom::URLLoaderFactory>* out_factory)
|
mojo::PendingRemote<network::mojom::URLLoaderFactory>* out_factory)
|
||||||
override;
|
override;
|
||||||
|
std::unique_ptr<content::LoginDelegate> CreateLoginDelegate(
|
||||||
|
const net::AuthChallengeInfo& auth_info,
|
||||||
|
content::WebContents* web_contents,
|
||||||
|
const content::GlobalRequestID& request_id,
|
||||||
|
bool is_main_frame,
|
||||||
|
const GURL& url,
|
||||||
|
scoped_refptr<net::HttpResponseHeaders> response_headers,
|
||||||
|
bool first_auth_attempt,
|
||||||
|
LoginAuthRequiredCallback auth_required_callback) override;
|
||||||
|
|
||||||
// content::RenderProcessHostObserver:
|
// content::RenderProcessHostObserver:
|
||||||
void RenderProcessHostDestroyed(content::RenderProcessHost* host) override;
|
void RenderProcessHostDestroyed(content::RenderProcessHost* host) override;
|
||||||
|
|
|
@ -181,13 +181,6 @@ void Browser::OnAccessibilitySupportChanged() {
|
||||||
observer.OnAccessibilitySupportChanged();
|
observer.OnAccessibilitySupportChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Browser::RequestLogin(
|
|
||||||
scoped_refptr<LoginHandler> login_handler,
|
|
||||||
std::unique_ptr<base::DictionaryValue> request_details) {
|
|
||||||
for (BrowserObserver& observer : observers_)
|
|
||||||
observer.OnLogin(login_handler, *(request_details.get()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Browser::PreMainMessageLoopRun() {
|
void Browser::PreMainMessageLoopRun() {
|
||||||
for (BrowserObserver& observer : observers_) {
|
for (BrowserObserver& observer : observers_) {
|
||||||
observer.OnPreMainMessageLoopRun();
|
observer.OnPreMainMessageLoopRun();
|
||||||
|
|
|
@ -250,10 +250,6 @@ class Browser : public WindowListObserver {
|
||||||
|
|
||||||
void OnAccessibilitySupportChanged();
|
void OnAccessibilitySupportChanged();
|
||||||
|
|
||||||
// Request basic auth login.
|
|
||||||
void RequestLogin(scoped_refptr<LoginHandler> login_handler,
|
|
||||||
std::unique_ptr<base::DictionaryValue> request_details);
|
|
||||||
|
|
||||||
void PreMainMessageLoopRun();
|
void PreMainMessageLoopRun();
|
||||||
|
|
||||||
// Stores the supplied |quit_closure|, to be run when the last Browser
|
// Stores the supplied |quit_closure|, to be run when the last Browser
|
||||||
|
|
|
@ -49,10 +49,6 @@ class BrowserObserver : public base::CheckedObserver {
|
||||||
virtual void OnWillFinishLaunching() {}
|
virtual void OnWillFinishLaunching() {}
|
||||||
virtual void OnFinishLaunching(const base::DictionaryValue& launch_info) {}
|
virtual void OnFinishLaunching(const base::DictionaryValue& launch_info) {}
|
||||||
|
|
||||||
// The browser requests HTTP login.
|
|
||||||
virtual void OnLogin(scoped_refptr<LoginHandler> login_handler,
|
|
||||||
const base::DictionaryValue& request_details) {}
|
|
||||||
|
|
||||||
// The browser's accessibility suppport has changed.
|
// The browser's accessibility suppport has changed.
|
||||||
virtual void OnAccessibilitySupportChanged() {}
|
virtual void OnAccessibilitySupportChanged() {}
|
||||||
|
|
||||||
|
|
|
@ -9,157 +9,78 @@
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "base/task/post_task.h"
|
#include "base/callback.h"
|
||||||
#include "base/values.h"
|
#include "gin/dictionary.h"
|
||||||
#include "content/public/browser/browser_task_traits.h"
|
#include "shell/browser/api/atom_api_web_contents.h"
|
||||||
#include "content/public/browser/browser_thread.h"
|
#include "shell/common/gin_converters/callback_converter.h"
|
||||||
#include "content/public/browser/web_contents.h"
|
|
||||||
#include "net/base/auth.h"
|
|
||||||
#include "net/base/upload_bytes_element_reader.h"
|
|
||||||
#include "net/base/upload_data_stream.h"
|
|
||||||
#include "net/base/upload_element_reader.h"
|
|
||||||
#include "net/base/upload_file_element_reader.h"
|
|
||||||
#include "shell/browser/browser.h"
|
|
||||||
#include "shell/common/gin_converters/net_converter.h"
|
#include "shell/common/gin_converters/net_converter.h"
|
||||||
|
#include "shell/common/gin_converters/value_converter.h"
|
||||||
|
|
||||||
using content::BrowserThread;
|
using content::BrowserThread;
|
||||||
|
|
||||||
namespace electron {
|
namespace electron {
|
||||||
|
|
||||||
namespace {
|
LoginHandler::LoginHandler(
|
||||||
|
|
||||||
void GetUploadData(base::ListValue* upload_data_list,
|
|
||||||
const net::URLRequest* request) {
|
|
||||||
const net::UploadDataStream* upload_data = request->get_upload_for_testing();
|
|
||||||
if (!upload_data)
|
|
||||||
return;
|
|
||||||
const std::vector<std::unique_ptr<net::UploadElementReader>>* readers =
|
|
||||||
upload_data->GetElementReaders();
|
|
||||||
for (const auto& reader : *readers) {
|
|
||||||
auto upload_data_dict = std::make_unique<base::DictionaryValue>();
|
|
||||||
if (reader->AsBytesReader()) {
|
|
||||||
const net::UploadBytesElementReader* bytes_reader =
|
|
||||||
reader->AsBytesReader();
|
|
||||||
auto bytes = std::make_unique<base::Value>(
|
|
||||||
std::vector<char>(bytes_reader->bytes(),
|
|
||||||
bytes_reader->bytes() + bytes_reader->length()));
|
|
||||||
upload_data_dict->Set("bytes", std::move(bytes));
|
|
||||||
} else if (reader->AsFileReader()) {
|
|
||||||
const net::UploadFileElementReader* file_reader = reader->AsFileReader();
|
|
||||||
auto file_path = file_reader->path().AsUTF8Unsafe();
|
|
||||||
upload_data_dict->SetKey("file", base::Value(file_path));
|
|
||||||
}
|
|
||||||
// else {
|
|
||||||
// const storage::UploadBlobElementReader* blob_reader =
|
|
||||||
// static_cast<storage::UploadBlobElementReader*>(reader.get());
|
|
||||||
// upload_data_dict->SetString("blobUUID", blob_reader->uuid());
|
|
||||||
// }
|
|
||||||
upload_data_list->Append(std::move(upload_data_dict));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void FillRequestDetails(base::DictionaryValue* details,
|
|
||||||
const net::URLRequest* request) {
|
|
||||||
details->SetString("method", request->method());
|
|
||||||
std::string url;
|
|
||||||
if (!request->url_chain().empty())
|
|
||||||
url = request->url().spec();
|
|
||||||
details->SetKey("url", base::Value(url));
|
|
||||||
details->SetString("referrer", request->referrer());
|
|
||||||
auto list = std::make_unique<base::ListValue>();
|
|
||||||
GetUploadData(list.get(), request);
|
|
||||||
if (!list->empty())
|
|
||||||
details->Set("uploadData", std::move(list));
|
|
||||||
auto headers_value = std::make_unique<base::DictionaryValue>();
|
|
||||||
for (net::HttpRequestHeaders::Iterator it(request->extra_request_headers());
|
|
||||||
it.GetNext();) {
|
|
||||||
headers_value->SetString(it.name(), it.value());
|
|
||||||
}
|
|
||||||
details->Set("headers", std::move(headers_value));
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
LoginHandler::LoginHandler(net::URLRequest* request,
|
|
||||||
const net::AuthChallengeInfo& auth_info,
|
const net::AuthChallengeInfo& auth_info,
|
||||||
// net::NetworkDelegate::AuthCallback callback,
|
content::WebContents* web_contents,
|
||||||
net::AuthCredentials* credentials)
|
bool is_main_frame,
|
||||||
: credentials_(credentials),
|
const GURL& url,
|
||||||
auth_info_(std::make_unique<net::AuthChallengeInfo>(auth_info)),
|
scoped_refptr<net::HttpResponseHeaders> response_headers,
|
||||||
// auth_callback_(std::move(callback)),
|
bool first_auth_attempt,
|
||||||
weak_factory_(this) {
|
LoginAuthRequiredCallback auth_required_callback)
|
||||||
DCHECK_CURRENTLY_ON(BrowserThread::IO);
|
|
||||||
|
|
||||||
std::unique_ptr<base::DictionaryValue> request_details(
|
: WebContentsObserver(web_contents),
|
||||||
new base::DictionaryValue);
|
auth_required_callback_(std::move(auth_required_callback)) {
|
||||||
// TODO(zcbenz): Use the converters from net_converter.
|
DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
||||||
FillRequestDetails(request_details.get(), request);
|
|
||||||
|
|
||||||
// TODO(deepak1556): fix with network service
|
|
||||||
// tracking issue: #19602
|
|
||||||
CHECK(false) << "fix with network service";
|
|
||||||
// web_contents_getter_ =
|
|
||||||
// resource_request_info->GetWebContentsGetterForRequest();
|
|
||||||
|
|
||||||
base::PostTask(
|
base::PostTask(
|
||||||
FROM_HERE, {BrowserThread::UI},
|
FROM_HERE, {base::CurrentThread()},
|
||||||
base::BindOnce(&Browser::RequestLogin, base::Unretained(Browser::Get()),
|
base::BindOnce(&LoginHandler::EmitEvent, weak_factory_.GetWeakPtr(),
|
||||||
base::RetainedRef(this), std::move(request_details)));
|
auth_info, is_main_frame, url, response_headers,
|
||||||
|
first_auth_attempt));
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoginHandler::EmitEvent(
|
||||||
|
net::AuthChallengeInfo auth_info,
|
||||||
|
bool is_main_frame,
|
||||||
|
const GURL& url,
|
||||||
|
scoped_refptr<net::HttpResponseHeaders> response_headers,
|
||||||
|
bool first_auth_attempt) {
|
||||||
|
v8::Isolate* isolate = v8::Isolate::GetCurrent();
|
||||||
|
|
||||||
|
auto api_web_contents = api::WebContents::From(isolate, web_contents());
|
||||||
|
if (api_web_contents.IsEmpty()) {
|
||||||
|
std::move(auth_required_callback_).Run(base::nullopt);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
v8::HandleScope scope(isolate);
|
||||||
|
|
||||||
|
auto details = gin::Dictionary::CreateEmpty(isolate);
|
||||||
|
details.Set("url", url.spec());
|
||||||
|
|
||||||
|
// These parameters aren't documented, and I'm not sure that they're useful,
|
||||||
|
// but we might as well stick 'em on the details object. If it turns out they
|
||||||
|
// are useful, we can add them to the docs :)
|
||||||
|
details.Set("isMainFrame", is_main_frame);
|
||||||
|
details.Set("firstAuthAttempt", first_auth_attempt);
|
||||||
|
details.Set("responseHeaders", response_headers.get());
|
||||||
|
|
||||||
|
bool default_prevented =
|
||||||
|
api_web_contents->Emit("login", std::move(details), auth_info,
|
||||||
|
base::BindOnce(&LoginHandler::CallbackFromJS,
|
||||||
|
weak_factory_.GetWeakPtr()));
|
||||||
|
if (!default_prevented) {
|
||||||
|
std::move(auth_required_callback_).Run(base::nullopt);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LoginHandler::~LoginHandler() = default;
|
LoginHandler::~LoginHandler() = default;
|
||||||
|
|
||||||
void LoginHandler::Login(const base::string16& username,
|
void LoginHandler::CallbackFromJS(base::string16 username,
|
||||||
const base::string16& password) {
|
base::string16 password) {
|
||||||
DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
std::move(auth_required_callback_)
|
||||||
|
.Run(net::AuthCredentials(username, password));
|
||||||
base::PostTask(
|
|
||||||
FROM_HERE, {BrowserThread::IO},
|
|
||||||
base::BindOnce(&LoginHandler::DoLogin, weak_factory_.GetWeakPtr(),
|
|
||||||
username, password));
|
|
||||||
}
|
|
||||||
|
|
||||||
void LoginHandler::CancelAuth() {
|
|
||||||
DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
|
||||||
|
|
||||||
base::PostTask(
|
|
||||||
FROM_HERE, {BrowserThread::IO},
|
|
||||||
base::BindOnce(&LoginHandler::DoCancelAuth, weak_factory_.GetWeakPtr()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void LoginHandler::NotifyRequestDestroyed() {
|
|
||||||
// auth_callback_.Reset();
|
|
||||||
credentials_ = nullptr;
|
|
||||||
weak_factory_.InvalidateWeakPtrs();
|
|
||||||
}
|
|
||||||
|
|
||||||
content::WebContents* LoginHandler::GetWebContents() const {
|
|
||||||
DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
|
||||||
// TODO(deepak1556): fix with network service
|
|
||||||
// tracking issue: #19602
|
|
||||||
CHECK(false) << "fix with network service";
|
|
||||||
return web_contents_getter_.Run();
|
|
||||||
}
|
|
||||||
|
|
||||||
void LoginHandler::DoCancelAuth() {
|
|
||||||
DCHECK_CURRENTLY_ON(BrowserThread::IO);
|
|
||||||
/*
|
|
||||||
if (!auth_callback_.is_null())
|
|
||||||
std::move(auth_callback_)
|
|
||||||
.Run(net::NetworkDelegate::AUTH_REQUIRED_RESPONSE_CANCEL_AUTH);
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
void LoginHandler::DoLogin(const base::string16& username,
|
|
||||||
const base::string16& password) {
|
|
||||||
DCHECK_CURRENTLY_ON(BrowserThread::IO);
|
|
||||||
/*
|
|
||||||
if (!auth_callback_.is_null()) {
|
|
||||||
credentials_->Set(username, password);
|
|
||||||
std::move(auth_callback_)
|
|
||||||
.Run(net::NetworkDelegate::AUTH_REQUIRED_RESPONSE_SET_AUTH);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace electron
|
} // namespace electron
|
||||||
|
|
|
@ -5,15 +5,11 @@
|
||||||
#ifndef SHELL_BROWSER_LOGIN_HANDLER_H_
|
#ifndef SHELL_BROWSER_LOGIN_HANDLER_H_
|
||||||
#define SHELL_BROWSER_LOGIN_HANDLER_H_
|
#define SHELL_BROWSER_LOGIN_HANDLER_H_
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include "base/callback.h"
|
|
||||||
#include "base/memory/ref_counted.h"
|
|
||||||
#include "base/memory/weak_ptr.h"
|
|
||||||
#include "base/sequenced_task_runner_helpers.h"
|
|
||||||
#include "base/strings/string16.h"
|
#include "base/strings/string16.h"
|
||||||
#include "content/public/browser/web_contents.h"
|
#include "base/values.h"
|
||||||
#include "net/base/network_delegate.h"
|
#include "content/public/browser/content_browser_client.h"
|
||||||
|
#include "content/public/browser/login_delegate.h"
|
||||||
|
#include "content/public/browser/web_contents_observer.h"
|
||||||
|
|
||||||
namespace content {
|
namespace content {
|
||||||
class WebContents;
|
class WebContents;
|
||||||
|
@ -21,52 +17,30 @@ class WebContents;
|
||||||
|
|
||||||
namespace electron {
|
namespace electron {
|
||||||
|
|
||||||
// Handles the HTTP basic auth, must be created on IO thread.
|
// Handles HTTP basic auth.
|
||||||
class LoginHandler : public base::RefCountedThreadSafe<LoginHandler> {
|
class LoginHandler : public content::LoginDelegate,
|
||||||
|
public content::WebContentsObserver {
|
||||||
public:
|
public:
|
||||||
LoginHandler(net::URLRequest* request,
|
LoginHandler(const net::AuthChallengeInfo& auth_info,
|
||||||
const net::AuthChallengeInfo& auth_info,
|
content::WebContents* web_contents,
|
||||||
// net::NetworkDelegate::AuthCallback callback,
|
bool is_main_frame,
|
||||||
net::AuthCredentials* credentials);
|
const GURL& url,
|
||||||
|
scoped_refptr<net::HttpResponseHeaders> response_headers,
|
||||||
// The auth is cancelled, must be called on UI thread.
|
bool first_auth_attempt,
|
||||||
void CancelAuth();
|
LoginAuthRequiredCallback auth_required_callback);
|
||||||
|
~LoginHandler() override;
|
||||||
// The URLRequest associated with the auth is destroyed.
|
|
||||||
void NotifyRequestDestroyed();
|
|
||||||
|
|
||||||
// Login with |username| and |password|, must be called on UI thread.
|
|
||||||
void Login(const base::string16& username, const base::string16& password);
|
|
||||||
|
|
||||||
// Returns the WebContents associated with the request, must be called on UI
|
|
||||||
// thread.
|
|
||||||
content::WebContents* GetWebContents() const;
|
|
||||||
|
|
||||||
const net::AuthChallengeInfo* auth_info() const { return auth_info_.get(); }
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class base::RefCountedThreadSafe<LoginHandler>;
|
void EmitEvent(net::AuthChallengeInfo auth_info,
|
||||||
friend class base::DeleteHelper<LoginHandler>;
|
bool is_main_frame,
|
||||||
|
const GURL& url,
|
||||||
|
scoped_refptr<net::HttpResponseHeaders> response_headers,
|
||||||
|
bool first_auth_attempt);
|
||||||
|
void CallbackFromJS(base::string16 username, base::string16 password);
|
||||||
|
|
||||||
~LoginHandler();
|
LoginAuthRequiredCallback auth_required_callback_;
|
||||||
|
|
||||||
// Must be called on IO thread.
|
base::WeakPtrFactory<LoginHandler> weak_factory_{this};
|
||||||
void DoCancelAuth();
|
|
||||||
void DoLogin(const base::string16& username, const base::string16& password);
|
|
||||||
|
|
||||||
// Credentials to be used for the auth.
|
|
||||||
net::AuthCredentials* credentials_;
|
|
||||||
|
|
||||||
// Who/where/what asked for the authentication.
|
|
||||||
std::unique_ptr<const net::AuthChallengeInfo> auth_info_;
|
|
||||||
|
|
||||||
// WebContents associated with the login request.
|
|
||||||
content::WebContents::Getter web_contents_getter_;
|
|
||||||
|
|
||||||
// Called with preferred value of net::NetworkDelegate::AuthRequiredResponse.
|
|
||||||
// net::NetworkDelegate::AuthCallback auth_callback_;
|
|
||||||
|
|
||||||
base::WeakPtrFactory<LoginHandler> weak_factory_;
|
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(LoginHandler);
|
DISALLOW_COPY_AND_ASSIGN(LoginHandler);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import * as cp from 'child_process'
|
import * as cp from 'child_process'
|
||||||
import * as https from 'https'
|
import * as https from 'https'
|
||||||
|
import * as http from 'http'
|
||||||
import * as net from 'net'
|
import * as net from 'net'
|
||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
import { app, BrowserWindow, Menu } from 'electron'
|
import { app, BrowserWindow, Menu } from 'electron'
|
||||||
import { emittedOnce } from './events-helpers'
|
import { emittedOnce } from './events-helpers'
|
||||||
import { closeWindow } from './window-helpers'
|
import { closeWindow, closeAllWindows } from './window-helpers'
|
||||||
import { ifdescribe } from './spec-helpers'
|
import { ifdescribe } from './spec-helpers'
|
||||||
import split = require('split')
|
import split = require('split')
|
||||||
|
|
||||||
|
@ -1415,6 +1416,33 @@ describe('default behavior', () => {
|
||||||
expect(output[0]).to.equal(output[1])
|
expect(output[0]).to.equal(output[1])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('login event', () => {
|
||||||
|
afterEach(closeAllWindows)
|
||||||
|
let server: http.Server
|
||||||
|
let serverUrl: string
|
||||||
|
|
||||||
|
before((done) => {
|
||||||
|
server = http.createServer((request, response) => {
|
||||||
|
if (request.headers.authorization) {
|
||||||
|
return response.end('ok')
|
||||||
|
}
|
||||||
|
response
|
||||||
|
.writeHead(401, { 'WWW-Authenticate': 'Basic realm="Foo"' })
|
||||||
|
.end()
|
||||||
|
}).listen(0, '127.0.0.1', () => {
|
||||||
|
serverUrl = 'http://127.0.0.1:' + (server.address() as net.AddressInfo).port
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should emit a login event on app when a WebContents hits a 401', async () => {
|
||||||
|
const w = new BrowserWindow({ show: false })
|
||||||
|
w.loadURL(serverUrl)
|
||||||
|
const [, webContents] = await emittedOnce(app, 'login')
|
||||||
|
expect(webContents).to.equal(w.webContents)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
async function runTestApp (name: string, ...args: any[]) {
|
async function runTestApp (name: string, ...args: any[]) {
|
||||||
|
|
|
@ -313,11 +313,6 @@ describe('session module', () => {
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
customSession = session.fromPartition('proxyconfig')
|
customSession = session.fromPartition('proxyconfig')
|
||||||
// FIXME(deepak1556): This is just a hack to force
|
|
||||||
// creation of request context which in turn initializes
|
|
||||||
// the network context, can be removed with network
|
|
||||||
// service enabled.
|
|
||||||
await customSession.clearHostResolverCache()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|
|
@ -1513,4 +1513,99 @@ describe('webContents module', () => {
|
||||||
await devtoolsClosed
|
await devtoolsClosed
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('login event', () => {
|
||||||
|
afterEach(closeAllWindows)
|
||||||
|
|
||||||
|
let server: http.Server
|
||||||
|
let serverUrl: string
|
||||||
|
let serverPort: number
|
||||||
|
let proxyServer: http.Server
|
||||||
|
let proxyServerPort: number
|
||||||
|
|
||||||
|
before((done) => {
|
||||||
|
server = http.createServer((request, response) => {
|
||||||
|
if (request.url === '/no-auth') {
|
||||||
|
return response.end('ok')
|
||||||
|
}
|
||||||
|
if (request.headers.authorization) {
|
||||||
|
response.writeHead(200, { 'Content-type': 'text/plain' })
|
||||||
|
return response.end(request.headers.authorization)
|
||||||
|
}
|
||||||
|
response
|
||||||
|
.writeHead(401, { 'WWW-Authenticate': 'Basic realm="Foo"' })
|
||||||
|
.end()
|
||||||
|
}).listen(0, '127.0.0.1', () => {
|
||||||
|
serverPort = (server.address() as AddressInfo).port
|
||||||
|
serverUrl = `http://127.0.0.1:${serverPort}`
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
before((done) => {
|
||||||
|
proxyServer = http.createServer((request, response) => {
|
||||||
|
if (request.headers['proxy-authorization']) {
|
||||||
|
response.writeHead(200, { 'Content-type': 'text/plain' })
|
||||||
|
return response.end(request.headers['proxy-authorization'])
|
||||||
|
}
|
||||||
|
response
|
||||||
|
.writeHead(407, { 'Proxy-Authenticate': 'Basic realm="Foo"' })
|
||||||
|
.end()
|
||||||
|
}).listen(0, '127.0.0.1', () => {
|
||||||
|
proxyServerPort = (proxyServer.address() as AddressInfo).port
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
server.close()
|
||||||
|
proxyServer.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('is emitted when navigating', async () => {
|
||||||
|
const [user, pass] = ['user', 'pass']
|
||||||
|
const w = new BrowserWindow({ show: false })
|
||||||
|
let eventRequest: any
|
||||||
|
let eventAuthInfo: any
|
||||||
|
w.webContents.on('login', (event, request, authInfo, cb) => {
|
||||||
|
eventRequest = request
|
||||||
|
eventAuthInfo = authInfo
|
||||||
|
event.preventDefault()
|
||||||
|
cb(user, pass)
|
||||||
|
})
|
||||||
|
await w.loadURL(serverUrl)
|
||||||
|
const body = await w.webContents.executeJavaScript(`document.documentElement.textContent`)
|
||||||
|
expect(body).to.equal(`Basic ${Buffer.from(`${user}:${pass}`).toString('base64')}`)
|
||||||
|
expect(eventRequest.url).to.equal(serverUrl + '/')
|
||||||
|
expect(eventAuthInfo.isProxy).to.be.false()
|
||||||
|
expect(eventAuthInfo.scheme).to.equal('basic')
|
||||||
|
expect(eventAuthInfo.host).to.equal('127.0.0.1')
|
||||||
|
expect(eventAuthInfo.port).to.equal(serverPort)
|
||||||
|
expect(eventAuthInfo.realm).to.equal('Foo')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('is emitted when a proxy requests authorization', async () => {
|
||||||
|
const customSession = session.fromPartition(`${Math.random()}`)
|
||||||
|
await customSession.setProxy({ proxyRules: `127.0.0.1:${proxyServerPort}`, proxyBypassRules: '<-loopback>' })
|
||||||
|
const [user, pass] = ['user', 'pass']
|
||||||
|
const w = new BrowserWindow({ show: false, webPreferences: { session: customSession } })
|
||||||
|
let eventRequest: any
|
||||||
|
let eventAuthInfo: any
|
||||||
|
w.webContents.on('login', (event, request, authInfo, cb) => {
|
||||||
|
eventRequest = request
|
||||||
|
eventAuthInfo = authInfo
|
||||||
|
event.preventDefault()
|
||||||
|
cb(user, pass)
|
||||||
|
})
|
||||||
|
await w.loadURL(`${serverUrl}/no-auth`)
|
||||||
|
const body = await w.webContents.executeJavaScript(`document.documentElement.textContent`)
|
||||||
|
expect(body).to.equal(`Basic ${Buffer.from(`${user}:${pass}`).toString('base64')}`)
|
||||||
|
expect(eventRequest.url).to.equal(`${serverUrl}/no-auth`)
|
||||||
|
expect(eventAuthInfo.isProxy).to.be.true()
|
||||||
|
expect(eventAuthInfo.scheme).to.equal('basic')
|
||||||
|
expect(eventAuthInfo.host).to.equal('127.0.0.1')
|
||||||
|
expect(eventAuthInfo.port).to.equal(proxyServerPort)
|
||||||
|
expect(eventAuthInfo.realm).to.equal('Foo')
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue