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…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Jeremy Apthorp
				Jeremy Apthorp