blob: 5c03bfd35e9af865f7741af0da68ff882b135659 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/signin/chrome_signin_proxying_url_loader_factory.h"
#include "base/barrier_closure.h"
#include "base/task/post_task.h"
#include "build/buildflag.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/chrome_signin_helper.h"
#include "chrome/browser/signin/header_modification_delegate.h"
#include "chrome/browser/signin/header_modification_delegate_impl.h"
#include "components/signin/core/browser/signin_header_helper.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/resource_context.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/guest_view/web_view/web_view_renderer_state.h"
#include "extensions/buildflags/buildflags.h"
#include "google_apis/gaia/gaia_auth_util.h"
#include "net/base/net_errors.h"
namespace signin {
namespace {
// User data key for ResourceContextData.
const void* const kResourceContextUserDataKey = &kResourceContextUserDataKey;
// Owns all of the ProxyingURLLoaderFactorys for a given Profile. Since these
// live on the IO thread this is done indirectly through the
// content::ResourceContext.
class ResourceContextData : public base::SupportsUserData::Data {
public:
~ResourceContextData() override {}
static void StartProxying(
content::ResourceContext* resource_context,
content::ResourceRequestInfo::WebContentsGetter web_contents_getter,
network::mojom::URLLoaderFactoryRequest request,
network::mojom::URLLoaderFactoryPtrInfo target_factory) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
auto* self = static_cast<ResourceContextData*>(
resource_context->GetUserData(kResourceContextUserDataKey));
if (!self) {
self = new ResourceContextData();
resource_context->SetUserData(kResourceContextUserDataKey,
base::WrapUnique(self));
}
auto delegate =
std::make_unique<HeaderModificationDelegateImpl>(resource_context);
auto proxy = std::make_unique<ProxyingURLLoaderFactory>(
std::move(delegate), std::move(web_contents_getter), std::move(request),
std::move(target_factory),
base::BindOnce(&ResourceContextData::RemoveProxy,
self->weak_factory_.GetWeakPtr()));
self->proxies_.emplace(std::move(proxy));
}
void RemoveProxy(ProxyingURLLoaderFactory* proxy) {
auto it = proxies_.find(proxy);
DCHECK(it != proxies_.end());
proxies_.erase(it);
}
private:
ResourceContextData() : weak_factory_(this) {}
std::set<std::unique_ptr<ProxyingURLLoaderFactory>, base::UniquePtrComparator>
proxies_;
base::WeakPtrFactory<ResourceContextData> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(ResourceContextData);
};
} // namespace
class ProxyingURLLoaderFactory::InProgressRequest
: public network::mojom::URLLoader,
public network::mojom::URLLoaderClient {
public:
InProgressRequest(
ProxyingURLLoaderFactory* factory,
network::mojom::URLLoaderRequest loader_request,
int32_t routing_id,
int32_t request_id,
uint32_t options,
const network::ResourceRequest& request,
network::mojom::URLLoaderClientPtr client,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation);
~InProgressRequest() override {
if (destruction_callback_)
std::move(destruction_callback_).Run();
}
// network::mojom::URLLoader:
void FollowRedirect(const std::vector<std::string>& removed_headers,
const net::HttpRequestHeaders& modified_headers,
const base::Optional<GURL>& new_url) override;
void ProceedWithResponse() override { target_loader_->ProceedWithResponse(); }
void SetPriority(net::RequestPriority priority,
int32_t intra_priority_value) override {
target_loader_->SetPriority(priority, intra_priority_value);
}
void PauseReadingBodyFromNet() override {
target_loader_->PauseReadingBodyFromNet();
}
void ResumeReadingBodyFromNet() override {
target_loader_->ResumeReadingBodyFromNet();
}
// network::mojom::URLLoaderClient:
void OnReceiveResponse(const network::ResourceResponseHead& head) override;
void OnReceiveRedirect(const net::RedirectInfo& redirect_info,
const network::ResourceResponseHead& head) override;
void OnUploadProgress(int64_t current_position,
int64_t total_size,
OnUploadProgressCallback callback) override {
target_client_->OnUploadProgress(current_position, total_size,
std::move(callback));
}
void OnReceiveCachedMetadata(const std::vector<uint8_t>& data) override {
target_client_->OnReceiveCachedMetadata(data);
}
void OnTransferSizeUpdated(int32_t transfer_size_diff) override {
target_client_->OnTransferSizeUpdated(transfer_size_diff);
}
void OnStartLoadingResponseBody(
mojo::ScopedDataPipeConsumerHandle body) override {
target_client_->OnStartLoadingResponseBody(std::move(body));
}
void OnComplete(const network::URLLoaderCompletionStatus& status) override {
target_client_->OnComplete(status);
}
private:
class ProxyRequestAdapter;
class ProxyResponseAdapter;
void OnBindingsClosed() {
// Destroys |this|.
factory_->RemoveRequest(this);
}
// Back pointer to the factory which owns this class.
ProxyingURLLoaderFactory* const factory_;
// Information about the current request.
GURL request_url_;
GURL response_url_;
GURL referrer_origin_;
net::HttpRequestHeaders headers_;
net::RedirectInfo redirect_info_;
const content::ResourceType resource_type_;
const bool is_main_frame_;
base::OnceClosure destruction_callback_;
// Messages received by |client_binding_| are forwarded to |target_client_|.
mojo::Binding<network::mojom::URLLoaderClient> client_binding_;
network::mojom::URLLoaderClientPtr target_client_;
// Messages received by |loader_binding_| are forwarded to |target_loader_|.
mojo::Binding<network::mojom::URLLoader> loader_binding_;
network::mojom::URLLoaderPtr target_loader_;
DISALLOW_COPY_AND_ASSIGN(InProgressRequest);
};
class ProxyingURLLoaderFactory::InProgressRequest::ProxyRequestAdapter
: public ChromeRequestAdapter {
public:
ProxyRequestAdapter(InProgressRequest* in_progress_request,
const net::HttpRequestHeaders& original_headers,
net::HttpRequestHeaders* modified_headers,
std::vector<std::string>* removed_headers)
: ChromeRequestAdapter(nullptr),
in_progress_request_(in_progress_request),
original_headers_(original_headers),
modified_headers_(modified_headers),
removed_headers_(removed_headers) {
DCHECK(in_progress_request_);
DCHECK(modified_headers_);
DCHECK(removed_headers_);
}
~ProxyRequestAdapter() override = default;
// signin::ChromeRequestAdapter
bool IsMainRequestContext(ProfileIOData* io_data) override {
// This code is never reached from other request contexts.
return true;
}
content::ResourceRequestInfo::WebContentsGetter GetWebContentsGetter()
const override {
return in_progress_request_->factory_->web_contents_getter_;
}
content::ResourceType GetResourceType() const override {
return in_progress_request_->resource_type_;
}
GURL GetReferrerOrigin() const override {
return in_progress_request_->referrer_origin_;
}
void SetDestructionCallback(base::OnceClosure closure) override {
if (!in_progress_request_->destruction_callback_)
in_progress_request_->destruction_callback_ = std::move(closure);
}
// signin::RequestAdapter
const GURL& GetUrl() override { return in_progress_request_->request_url_; }
bool HasHeader(const std::string& name) override {
return (original_headers_.HasHeader(name) ||
modified_headers_->HasHeader(name)) &&
!base::ContainsValue(*removed_headers_, name);
}
void RemoveRequestHeaderByName(const std::string& name) override {
if (!base::ContainsValue(*removed_headers_, name))
removed_headers_->push_back(name);
}
void SetExtraHeaderByName(const std::string& name,
const std::string& value) override {
modified_headers_->SetHeader(name, value);
auto it =
std::find(removed_headers_->begin(), removed_headers_->end(), name);
if (it != removed_headers_->end())
removed_headers_->erase(it);
}
private:
InProgressRequest* const in_progress_request_;
const net::HttpRequestHeaders& original_headers_;
net::HttpRequestHeaders* modified_headers_;
std::vector<std::string>* removed_headers_;
DISALLOW_COPY_AND_ASSIGN(ProxyRequestAdapter);
};
class ProxyingURLLoaderFactory::InProgressRequest::ProxyResponseAdapter
: public ResponseAdapter {
public:
ProxyResponseAdapter(const InProgressRequest* in_progress_request,
net::HttpResponseHeaders* headers)
: ResponseAdapter(nullptr),
in_progress_request_(in_progress_request),
headers_(headers) {
DCHECK(in_progress_request_);
DCHECK(headers_);
}
~ProxyResponseAdapter() override = default;
// signin::ResponseAdapter
content::ResourceRequestInfo::WebContentsGetter GetWebContentsGetter()
const override {
return in_progress_request_->factory_->web_contents_getter_;
}
bool IsMainFrame() const override {
return in_progress_request_->is_main_frame_;
}
GURL GetOrigin() const override {
return in_progress_request_->response_url_.GetOrigin();
}
const net::HttpResponseHeaders* GetHeaders() const override {
return headers_;
}
void RemoveHeader(const std::string& name) override {
headers_->RemoveHeader(name);
}
private:
const InProgressRequest* const in_progress_request_;
net::HttpResponseHeaders* const headers_;
DISALLOW_COPY_AND_ASSIGN(ProxyResponseAdapter);
};
ProxyingURLLoaderFactory::InProgressRequest::InProgressRequest(
ProxyingURLLoaderFactory* factory,
network::mojom::URLLoaderRequest loader_request,
int32_t routing_id,
int32_t request_id,
uint32_t options,
const network::ResourceRequest& request,
network::mojom::URLLoaderClientPtr client,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation)
: factory_(factory),
request_url_(request.url),
response_url_(request.url),
referrer_origin_(request.referrer.GetOrigin()),
resource_type_(static_cast<content::ResourceType>(request.resource_type)),
is_main_frame_(request.is_main_frame),
client_binding_(this),
target_client_(std::move(client)),
loader_binding_(this, std::move(loader_request)) {
network::mojom::URLLoaderClientPtr proxy_client;
client_binding_.Bind(mojo::MakeRequest(&proxy_client));
net::HttpRequestHeaders modified_headers;
std::vector<std::string> removed_headers;
ProxyRequestAdapter adapter(this, request.headers, &modified_headers,
&removed_headers);
factory_->delegate_->ProcessRequest(&adapter, GURL() /* redirect_url */);
if (modified_headers.IsEmpty() && removed_headers.empty()) {
factory_->target_factory_->CreateLoaderAndStart(
mojo::MakeRequest(&target_loader_), routing_id, request_id, options,
request, std::move(proxy_client), traffic_annotation);
// We need to keep a full copy of the request headers in case there is a
// redirect and the request headers need to be modified again.
headers_.CopyFrom(request.headers);
} else {
network::ResourceRequest request_copy = request;
request_copy.headers.MergeFrom(modified_headers);
for (const std::string& name : removed_headers)
request_copy.headers.RemoveHeader(name);
factory_->target_factory_->CreateLoaderAndStart(
mojo::MakeRequest(&target_loader_), routing_id, request_id, options,
request_copy, std::move(proxy_client), traffic_annotation);
headers_.Swap(&request_copy.headers);
}
base::RepeatingClosure closure = base::BarrierClosure(
2, base::BindOnce(&InProgressRequest::OnBindingsClosed,
base::Unretained(this)));
loader_binding_.set_connection_error_handler(closure);
client_binding_.set_connection_error_handler(closure);
}
void ProxyingURLLoaderFactory::InProgressRequest::FollowRedirect(
const std::vector<std::string>& removed_headers_ext,
const net::HttpRequestHeaders& modified_headers_ext,
const base::Optional<GURL>& opt_new_url) {
std::vector<std::string> removed_headers = removed_headers_ext;
net::HttpRequestHeaders modified_headers = modified_headers_ext;
ProxyRequestAdapter adapter(this, headers_, &modified_headers,
&removed_headers);
factory_->delegate_->ProcessRequest(&adapter, redirect_info_.new_url);
headers_.MergeFrom(modified_headers);
for (const std::string& name : removed_headers)
headers_.RemoveHeader(name);
target_loader_->FollowRedirect(removed_headers, modified_headers,
opt_new_url);
request_url_ = redirect_info_.new_url;
referrer_origin_ = GURL(redirect_info_.new_referrer).GetOrigin();
}
void ProxyingURLLoaderFactory::InProgressRequest::OnReceiveResponse(
const network::ResourceResponseHead& head) {
// Even though |head| is const we can get a non-const pointer to the headers
// and modifications we made are passed to the target client.
ProxyResponseAdapter adapter(this, head.headers.get());
factory_->delegate_->ProcessResponse(&adapter, GURL() /* redirect_url */);
target_client_->OnReceiveResponse(head);
}
void ProxyingURLLoaderFactory::InProgressRequest::OnReceiveRedirect(
const net::RedirectInfo& redirect_info,
const network::ResourceResponseHead& head) {
// Even though |head| is const we can get a non-const pointer to the headers
// and modifications we made are passed to the target client.
ProxyResponseAdapter adapter(this, head.headers.get());
factory_->delegate_->ProcessResponse(&adapter, redirect_info.new_url);
target_client_->OnReceiveRedirect(redirect_info, head);
// The request URL returned by ProxyResponseAdapter::GetOrigin() is updated
// immediately but the URL and referrer
redirect_info_ = redirect_info;
response_url_ = redirect_info.new_url;
}
ProxyingURLLoaderFactory::ProxyingURLLoaderFactory(
std::unique_ptr<HeaderModificationDelegate> delegate,
content::ResourceRequestInfo::WebContentsGetter web_contents_getter,
network::mojom::URLLoaderFactoryRequest loader_request,
network::mojom::URLLoaderFactoryPtrInfo target_factory,
DisconnectCallback on_disconnect) {
DCHECK(proxy_bindings_.empty());
DCHECK(!target_factory_.is_bound());
DCHECK(!delegate_);
DCHECK(!web_contents_getter_);
DCHECK(!on_disconnect_);
delegate_ = std::move(delegate);
web_contents_getter_ = std::move(web_contents_getter);
on_disconnect_ = std::move(on_disconnect);
target_factory_.Bind(std::move(target_factory));
target_factory_.set_connection_error_handler(base::BindOnce(
&ProxyingURLLoaderFactory::OnTargetFactoryError, base::Unretained(this)));
proxy_bindings_.AddBinding(this, std::move(loader_request));
proxy_bindings_.set_connection_error_handler(base::BindRepeating(
&ProxyingURLLoaderFactory::OnProxyBindingError, base::Unretained(this)));
}
ProxyingURLLoaderFactory::~ProxyingURLLoaderFactory() = default;
// static
bool ProxyingURLLoaderFactory::MaybeProxyRequest(
content::RenderFrameHost* render_frame_host,
bool is_navigation,
const url::Origin& request_initiator,
network::mojom::URLLoaderFactoryRequest* factory_request) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Navigation requests are handled using signin::URLLoaderThrottle.
if (is_navigation)
return false;
// This proxy should only be installed for subresource requests from a frame
// that is rendering the GAIA signon realm.
if (!gaia::IsGaiaSignonRealm(request_initiator.GetURL()))
return false;
auto* web_contents =
content::WebContents::FromRenderFrameHost(render_frame_host);
auto* profile =
Profile::FromBrowserContext(web_contents->GetBrowserContext());
if (profile->IsOffTheRecord())
return false;
#if BUILDFLAG(ENABLE_EXTENSIONS)
// Account consistency requires the AccountReconcilor, which is only
// attached to the main request context.
// Note: InlineLoginUI uses an isolated request context and thus bypasses
// the account consistency flow here. See http://crbug.com/428396
if (extensions::WebViewRendererState::GetInstance()->IsGuest(
render_frame_host->GetProcess()->GetID())) {
return false;
}
#endif
auto proxied_request = std::move(*factory_request);
network::mojom::URLLoaderFactoryPtrInfo target_factory_info;
*factory_request = mojo::MakeRequest(&target_factory_info);
auto web_contents_getter =
base::BindRepeating(&content::WebContents::FromFrameTreeNodeId,
render_frame_host->GetFrameTreeNodeId());
base::PostTaskWithTraits(
FROM_HERE, {content::BrowserThread::IO},
base::BindOnce(&ResourceContextData::StartProxying,
profile->GetResourceContext(),
std::move(web_contents_getter), std::move(proxied_request),
std::move(target_factory_info)));
return true;
}
void ProxyingURLLoaderFactory::CreateLoaderAndStart(
network::mojom::URLLoaderRequest loader_request,
int32_t routing_id,
int32_t request_id,
uint32_t options,
const network::ResourceRequest& request,
network::mojom::URLLoaderClientPtr client,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) {
requests_.insert(std::make_unique<InProgressRequest>(
this, std::move(loader_request), routing_id, request_id, options, request,
std::move(client), traffic_annotation));
}
void ProxyingURLLoaderFactory::Clone(
network::mojom::URLLoaderFactoryRequest loader_request) {
proxy_bindings_.AddBinding(this, std::move(loader_request));
}
void ProxyingURLLoaderFactory::OnTargetFactoryError() {
// Stop calls to CreateLoaderAndStart() when |target_factory_| is invalid.
target_factory_.reset();
proxy_bindings_.CloseAllBindings();
MaybeDestroySelf();
}
void ProxyingURLLoaderFactory::OnProxyBindingError() {
if (proxy_bindings_.empty())
target_factory_.reset();
MaybeDestroySelf();
}
void ProxyingURLLoaderFactory::RemoveRequest(InProgressRequest* request) {
auto it = requests_.find(request);
DCHECK(it != requests_.end());
requests_.erase(it);
MaybeDestroySelf();
}
void ProxyingURLLoaderFactory::MaybeDestroySelf() {
// Even if all URLLoaderFactory pipes connected to this object have been
// closed it has to stay alive until all active requests have completed.
if (target_factory_.is_bound() || !requests_.empty())
return;
// Deletes |this|.
std::move(on_disconnect_).Run(this);
}
} // namespace signin