mirror of
https://github.com/electron/electron.git
synced 2026-02-19 03:14:51 -05:00
feat: support WebSocket authentication handling (#49064)
* feat: support WebSocket authentication handling * chore: make linter happy --------- Co-authored-by: Charles Kerr <charles@charleskerr.com>
This commit is contained in:
@@ -5,12 +5,14 @@
|
||||
#include "shell/browser/api/electron_api_web_request.h"
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include "base/containers/fixed_flat_map.h"
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "base/task/sequenced_task_runner.h"
|
||||
#include "base/values.h"
|
||||
#include "content/public/browser/web_contents.h"
|
||||
@@ -25,6 +27,7 @@
|
||||
#include "shell/browser/api/electron_api_web_frame_main.h"
|
||||
#include "shell/browser/electron_browser_context.h"
|
||||
#include "shell/browser/javascript_environment.h"
|
||||
#include "shell/browser/login_handler.h"
|
||||
#include "shell/common/gin_converters/callback_converter.h"
|
||||
#include "shell/common/gin_converters/frame_converter.h"
|
||||
#include "shell/common/gin_converters/gurl_converter.h"
|
||||
@@ -108,7 +111,7 @@ v8::Local<v8::Value> HttpResponseHeadersToV8(
|
||||
|
||||
// Overloaded by multiple types to fill the |details| object.
|
||||
void ToDictionary(gin_helper::Dictionary* details,
|
||||
extensions::WebRequestInfo* info) {
|
||||
const extensions::WebRequestInfo* info) {
|
||||
details->Set("id", info->id);
|
||||
details->Set("url", info->url);
|
||||
details->Set("method", info->method);
|
||||
@@ -255,7 +258,7 @@ bool WebRequest::RequestFilter::MatchesType(
|
||||
}
|
||||
|
||||
bool WebRequest::RequestFilter::MatchesRequest(
|
||||
extensions::WebRequestInfo* info) const {
|
||||
const extensions::WebRequestInfo* info) const {
|
||||
// Matches URL and type, and does not match exclude URL.
|
||||
return MatchesURL(info->url, include_url_patterns_) &&
|
||||
!MatchesURL(info->url, exclude_url_patterns_) &&
|
||||
@@ -287,6 +290,10 @@ struct WebRequest::BlockedRequest {
|
||||
net::CompletionOnceCallback callback;
|
||||
// Only used for onBeforeSendHeaders.
|
||||
BeforeSendHeadersCallback before_send_headers_callback;
|
||||
// The callback to invoke for auth. If |auth_callback.is_null()| is false,
|
||||
// |callback| must be NULL.
|
||||
// Only valid for OnAuthRequired.
|
||||
AuthCallback auth_callback;
|
||||
// Only used for onBeforeSendHeaders.
|
||||
raw_ptr<net::HttpRequestHeaders> request_headers = nullptr;
|
||||
// Only used for onHeadersReceived.
|
||||
@@ -297,6 +304,8 @@ struct WebRequest::BlockedRequest {
|
||||
std::string status_line;
|
||||
// Only used for onBeforeRequest.
|
||||
raw_ptr<GURL> new_url = nullptr;
|
||||
// Owns the LoginHandler while waiting for auth credentials.
|
||||
std::unique_ptr<LoginHandler> login_handler;
|
||||
};
|
||||
|
||||
WebRequest::SimpleListenerInfo::SimpleListenerInfo(RequestFilter filter_,
|
||||
@@ -603,6 +612,36 @@ void WebRequest::OnSendHeaders(extensions::WebRequestInfo* info,
|
||||
HandleSimpleEvent(SimpleEvent::kOnSendHeaders, info, request, headers);
|
||||
}
|
||||
|
||||
WebRequest::AuthRequiredResponse WebRequest::OnAuthRequired(
|
||||
const extensions::WebRequestInfo* request_info,
|
||||
const net::AuthChallengeInfo& auth_info,
|
||||
WebRequest::AuthCallback callback,
|
||||
net::AuthCredentials* credentials) {
|
||||
content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(
|
||||
request_info->render_process_id, request_info->frame_routing_id);
|
||||
content::WebContents* web_contents = nullptr;
|
||||
if (rfh)
|
||||
web_contents = content::WebContents::FromRenderFrameHost(rfh);
|
||||
|
||||
BlockedRequest blocked_request;
|
||||
blocked_request.auth_callback = std::move(callback);
|
||||
blocked_requests_[request_info->id] = std::move(blocked_request);
|
||||
|
||||
auto login_callback =
|
||||
base::BindOnce(&WebRequest::OnLoginAuthResult, base::Unretained(this),
|
||||
request_info->id, credentials);
|
||||
|
||||
scoped_refptr<net::HttpResponseHeaders> response_headers =
|
||||
request_info->response_headers;
|
||||
blocked_requests_[request_info->id].login_handler =
|
||||
std::make_unique<LoginHandler>(
|
||||
auth_info, web_contents,
|
||||
static_cast<base::ProcessId>(request_info->render_process_id),
|
||||
request_info->url, response_headers, std::move(login_callback));
|
||||
|
||||
return AuthRequiredResponse::AUTH_REQUIRED_RESPONSE_IO_PENDING;
|
||||
}
|
||||
|
||||
void WebRequest::OnBeforeRedirect(extensions::WebRequestInfo* info,
|
||||
const network::ResourceRequest& request,
|
||||
const GURL& new_location) {
|
||||
@@ -732,6 +771,26 @@ void WebRequest::HandleSimpleEvent(SimpleEvent event,
|
||||
info.listener.Run(gin::ConvertToV8(isolate, details));
|
||||
}
|
||||
|
||||
void WebRequest::OnLoginAuthResult(
|
||||
uint64_t id,
|
||||
net::AuthCredentials* credentials,
|
||||
const std::optional<net::AuthCredentials>& maybe_creds) {
|
||||
auto iter = blocked_requests_.find(id);
|
||||
if (iter == blocked_requests_.end())
|
||||
NOTREACHED();
|
||||
|
||||
AuthRequiredResponse action =
|
||||
AuthRequiredResponse::AUTH_REQUIRED_RESPONSE_NO_ACTION;
|
||||
if (maybe_creds.has_value()) {
|
||||
*credentials = maybe_creds.value();
|
||||
action = AuthRequiredResponse::AUTH_REQUIRED_RESPONSE_SET_AUTH;
|
||||
}
|
||||
|
||||
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
|
||||
FROM_HERE, base::BindOnce(std::move(iter->second.auth_callback), action));
|
||||
blocked_requests_.erase(iter);
|
||||
}
|
||||
|
||||
// static
|
||||
gin_helper::Handle<WebRequest> WebRequest::FromOrCreate(
|
||||
v8::Isolate* isolate,
|
||||
|
||||
@@ -82,6 +82,11 @@ class WebRequest final : public gin_helper::DeprecatedWrappable<WebRequest>,
|
||||
void OnSendHeaders(extensions::WebRequestInfo* info,
|
||||
const network::ResourceRequest& request,
|
||||
const net::HttpRequestHeaders& headers) override;
|
||||
AuthRequiredResponse OnAuthRequired(
|
||||
const extensions::WebRequestInfo* info,
|
||||
const net::AuthChallengeInfo& auth_info,
|
||||
AuthCallback callback,
|
||||
net::AuthCredentials* credentials) override;
|
||||
void OnBeforeRedirect(extensions::WebRequestInfo* info,
|
||||
const network::ResourceRequest& request,
|
||||
const GURL& new_location) override;
|
||||
@@ -157,6 +162,12 @@ class WebRequest final : public gin_helper::DeprecatedWrappable<WebRequest>,
|
||||
v8::Local<v8::Value> response);
|
||||
void OnHeadersReceivedListenerResult(uint64_t id,
|
||||
v8::Local<v8::Value> response);
|
||||
// Callback invoked by LoginHandler when auth credentials are supplied via
|
||||
// the unified 'login' event. Bridges back into WebRequest's AuthCallback.
|
||||
void OnLoginAuthResult(
|
||||
uint64_t id,
|
||||
net::AuthCredentials* credentials,
|
||||
const std::optional<net::AuthCredentials>& maybe_creds);
|
||||
|
||||
class RequestFilter {
|
||||
public:
|
||||
@@ -174,7 +185,7 @@ class WebRequest final : public gin_helper::DeprecatedWrappable<WebRequest>,
|
||||
bool is_match_pattern = true);
|
||||
void AddType(extensions::WebRequestResourceType type);
|
||||
|
||||
bool MatchesRequest(extensions::WebRequestInfo* info) const;
|
||||
bool MatchesRequest(const extensions::WebRequestInfo* info) const;
|
||||
|
||||
private:
|
||||
bool MatchesURL(const GURL& url,
|
||||
|
||||
@@ -42,6 +42,23 @@ LoginHandler::LoginHandler(
|
||||
response_headers, first_auth_attempt));
|
||||
}
|
||||
|
||||
LoginHandler::LoginHandler(
|
||||
const net::AuthChallengeInfo& auth_info,
|
||||
content::WebContents* web_contents,
|
||||
base::ProcessId process_id,
|
||||
const GURL& url,
|
||||
scoped_refptr<net::HttpResponseHeaders> response_headers,
|
||||
content::LoginDelegate::LoginAuthRequiredCallback auth_required_callback)
|
||||
: LoginHandler(auth_info,
|
||||
web_contents,
|
||||
/*is_request_for_primary_main_frame=*/false,
|
||||
/*is_request_for_navigation=*/false,
|
||||
process_id,
|
||||
url,
|
||||
std::move(response_headers),
|
||||
/*first_auth_attempt=*/false,
|
||||
std::move(auth_required_callback)) {}
|
||||
|
||||
void LoginHandler::EmitEvent(
|
||||
net::AuthChallengeInfo auth_info,
|
||||
content::WebContents* web_contents,
|
||||
|
||||
@@ -32,6 +32,13 @@ class LoginHandler : public content::LoginDelegate {
|
||||
scoped_refptr<net::HttpResponseHeaders> response_headers,
|
||||
bool first_auth_attempt,
|
||||
content::LoginDelegate::LoginAuthRequiredCallback auth_required_callback);
|
||||
LoginHandler(
|
||||
const net::AuthChallengeInfo& auth_info,
|
||||
content::WebContents* web_contents,
|
||||
base::ProcessId process_id,
|
||||
const GURL& url,
|
||||
scoped_refptr<net::HttpResponseHeaders> response_headers,
|
||||
content::LoginDelegate::LoginAuthRequiredCallback auth_required_callback);
|
||||
~LoginHandler() override;
|
||||
|
||||
// disable copy
|
||||
|
||||
@@ -374,19 +374,21 @@ void ProxyingWebSocket::OnHeadersReceivedComplete(int error_code) {
|
||||
ContinueToCompleted();
|
||||
}
|
||||
|
||||
void ProxyingWebSocket::OnAuthRequiredComplete(AuthRequiredResponse rv) {
|
||||
void ProxyingWebSocket::OnAuthRequiredComplete(
|
||||
WebRequestAPI::AuthRequiredResponse rv) {
|
||||
CHECK(auth_required_callback_);
|
||||
ResumeIncomingMethodCallProcessing();
|
||||
switch (rv) {
|
||||
case AuthRequiredResponse::kNoAction:
|
||||
case AuthRequiredResponse::kCancelAuth:
|
||||
case WebRequestAPI::AuthRequiredResponse::AUTH_REQUIRED_RESPONSE_NO_ACTION:
|
||||
case WebRequestAPI::AuthRequiredResponse::
|
||||
AUTH_REQUIRED_RESPONSE_CANCEL_AUTH:
|
||||
std::move(auth_required_callback_).Run(std::nullopt);
|
||||
break;
|
||||
|
||||
case AuthRequiredResponse::kSetAuth:
|
||||
case WebRequestAPI::AuthRequiredResponse::AUTH_REQUIRED_RESPONSE_SET_AUTH:
|
||||
std::move(auth_required_callback_).Run(auth_credentials_);
|
||||
break;
|
||||
case AuthRequiredResponse::kIoPending:
|
||||
case WebRequestAPI::AuthRequiredResponse::AUTH_REQUIRED_RESPONSE_IO_PENDING:
|
||||
NOTREACHED();
|
||||
}
|
||||
}
|
||||
@@ -403,8 +405,13 @@ void ProxyingWebSocket::OnHeadersReceivedCompleteForAuth(
|
||||
|
||||
auto continuation = base::BindRepeating(
|
||||
&ProxyingWebSocket::OnAuthRequiredComplete, weak_factory_.GetWeakPtr());
|
||||
auto auth_rv = AuthRequiredResponse::kCancelAuth;
|
||||
auto auth_rv = web_request_api_->OnAuthRequired(
|
||||
&info_, auth_info, std::move(continuation), &auth_credentials_);
|
||||
PauseIncomingMethodCallProcessing();
|
||||
if (auth_rv ==
|
||||
WebRequestAPI::AuthRequiredResponse::AUTH_REQUIRED_RESPONSE_IO_PENDING) {
|
||||
return;
|
||||
}
|
||||
|
||||
OnAuthRequiredComplete(auth_rv);
|
||||
}
|
||||
|
||||
@@ -37,21 +37,6 @@ class ProxyingWebSocket : public network::mojom::WebSocketHandshakeClient,
|
||||
public:
|
||||
using WebSocketFactory = content::ContentBrowserClient::WebSocketFactory;
|
||||
|
||||
// AuthRequiredResponse indicates how an OnAuthRequired call is handled.
|
||||
enum class AuthRequiredResponse {
|
||||
// No credentials were provided.
|
||||
kNoAction,
|
||||
// AuthCredentials is filled in with a username and password, which should
|
||||
// be used in a response to the provided auth challenge.
|
||||
kSetAuth,
|
||||
// The request should be canceled.
|
||||
kCancelAuth,
|
||||
// The action will be decided asynchronously. |callback| will be invoked
|
||||
// when the decision is made, and one of the other AuthRequiredResponse
|
||||
// values will be passed in with the same semantics as described above.
|
||||
kIoPending,
|
||||
};
|
||||
|
||||
ProxyingWebSocket(
|
||||
WebRequestAPI* web_request_api,
|
||||
WebSocketFactory factory,
|
||||
@@ -121,7 +106,7 @@ class ProxyingWebSocket : public network::mojom::WebSocketHandshakeClient,
|
||||
void ContinueToStartRequest(int error_code);
|
||||
void OnHeadersReceivedComplete(int error_code);
|
||||
void ContinueToHeadersReceived();
|
||||
void OnAuthRequiredComplete(AuthRequiredResponse rv);
|
||||
void OnAuthRequiredComplete(WebRequestAPI::AuthRequiredResponse rv);
|
||||
void OnHeadersReceivedCompleteForAuth(const net::AuthChallengeInfo& auth_info,
|
||||
int rv);
|
||||
void ContinueToCompleted();
|
||||
|
||||
@@ -27,6 +27,23 @@ class WebRequestAPI {
|
||||
const std::set<std::string>& set_headers,
|
||||
int error_code)>;
|
||||
|
||||
// AuthRequiredResponse indicates how an OnAuthRequired call is handled.
|
||||
enum class AuthRequiredResponse {
|
||||
// No credentials were provided.
|
||||
AUTH_REQUIRED_RESPONSE_NO_ACTION,
|
||||
// AuthCredentials is filled in with a username and password, which should
|
||||
// be used in a response to the provided auth challenge.
|
||||
AUTH_REQUIRED_RESPONSE_SET_AUTH,
|
||||
// The request should be canceled.
|
||||
AUTH_REQUIRED_RESPONSE_CANCEL_AUTH,
|
||||
// The action will be decided asynchronously. |callback| will be invoked
|
||||
// when the decision is made, and one of the other AuthRequiredResponse
|
||||
// values will be passed in with the same semantics as described above.
|
||||
AUTH_REQUIRED_RESPONSE_IO_PENDING,
|
||||
};
|
||||
|
||||
using AuthCallback = base::OnceCallback<void(AuthRequiredResponse)>;
|
||||
|
||||
virtual bool HasListener() const = 0;
|
||||
virtual int OnBeforeRequest(extensions::WebRequestInfo* info,
|
||||
const network::ResourceRequest& request,
|
||||
@@ -36,6 +53,11 @@ class WebRequestAPI {
|
||||
const network::ResourceRequest& request,
|
||||
BeforeSendHeadersCallback callback,
|
||||
net::HttpRequestHeaders* headers) = 0;
|
||||
virtual AuthRequiredResponse OnAuthRequired(
|
||||
const extensions::WebRequestInfo* info,
|
||||
const net::AuthChallengeInfo& auth_info,
|
||||
AuthCallback callback,
|
||||
net::AuthCredentials* credentials) = 0;
|
||||
virtual int OnHeadersReceived(
|
||||
extensions::WebRequestInfo* info,
|
||||
const network::ResourceRequest& request,
|
||||
|
||||
Reference in New Issue
Block a user