diff options
Diffstat (limited to 'chromium/chrome/browser/signin/chrome_signin_proxying_url_loader_factory.cc')
-rw-r--r-- | chromium/chrome/browser/signin/chrome_signin_proxying_url_loader_factory.cc | 551 |
1 files changed, 551 insertions, 0 deletions
diff --git a/chromium/chrome/browser/signin/chrome_signin_proxying_url_loader_factory.cc b/chromium/chrome/browser/signin/chrome_signin_proxying_url_loader_factory.cc new file mode 100644 index 00000000000..d4b8d911a9e --- /dev/null +++ b/chromium/chrome/browser/signin/chrome_signin_proxying_url_loader_factory.cc @@ -0,0 +1,551 @@ +// 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/bind.h" +#include "base/memory/ptr_util.h" +#include "base/memory/raw_ptr.h" +#include "base/supports_user_data.h" +#include "build/build_config.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_thread.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/browser/render_process_host.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 "mojo/public/cpp/bindings/pending_receiver.h" +#include "mojo/public/cpp/bindings/receiver.h" +#include "net/base/net_errors.h" +#include "services/network/public/cpp/resource_request.h" +#include "services/network/public/mojom/early_hints.mojom.h" +#include "services/network/public/mojom/fetch_api.mojom-shared.h" +#include "services/network/public/mojom/url_loader.mojom.h" +#include "services/network/public/mojom/url_loader_factory.mojom.h" +#include "services/network/public/mojom/url_response_head.mojom.h" + +#if defined(OS_ANDROID) +#include "chrome/browser/android/tab_android.h" +#include "chrome/browser/android/tab_web_contents_delegate_android.h" +#endif + +namespace signin { + +namespace { + +// User data key for BrowserContextData. +const void* const kBrowserContextUserDataKey = &kBrowserContextUserDataKey; + +// Owns all of the ProxyingURLLoaderFactorys for a given Profile. +class BrowserContextData : public base::SupportsUserData::Data { + public: + BrowserContextData(const BrowserContextData&) = delete; + BrowserContextData& operator=(const BrowserContextData&) = delete; + + ~BrowserContextData() override {} + + static void StartProxying( + Profile* profile, + content::WebContents::Getter web_contents_getter, + mojo::PendingReceiver<network::mojom::URLLoaderFactory> receiver, + mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory) { + auto* self = static_cast<BrowserContextData*>( + profile->GetUserData(kBrowserContextUserDataKey)); + if (!self) { + self = new BrowserContextData(); + profile->SetUserData(kBrowserContextUserDataKey, base::WrapUnique(self)); + } + +#if defined(OS_ANDROID) + bool is_custom_tab = false; + content::WebContents* web_contents = web_contents_getter.Run(); + if (web_contents) { + auto* delegate = + TabAndroid::FromWebContents(web_contents) + ? static_cast<android::TabWebContentsDelegateAndroid*>( + web_contents->GetDelegate()) + : nullptr; + is_custom_tab = delegate && delegate->IsCustomTab(); + } + auto delegate = std::make_unique<HeaderModificationDelegateImpl>( + profile, /*incognito_enabled=*/!is_custom_tab); +#else + auto delegate = std::make_unique<HeaderModificationDelegateImpl>(profile); +#endif + auto proxy = std::make_unique<ProxyingURLLoaderFactory>( + std::move(delegate), std::move(web_contents_getter), + std::move(receiver), std::move(target_factory), + base::BindOnce(&BrowserContextData::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: + BrowserContextData() {} + + std::set<std::unique_ptr<ProxyingURLLoaderFactory>, base::UniquePtrComparator> + proxies_; + + base::WeakPtrFactory<BrowserContextData> weak_factory_{this}; +}; + +} // namespace + +class ProxyingURLLoaderFactory::InProgressRequest + : public network::mojom::URLLoader, + public network::mojom::URLLoaderClient, + public base::SupportsUserData { + public: + InProgressRequest( + ProxyingURLLoaderFactory* factory, + mojo::PendingReceiver<network::mojom::URLLoader> loader_receiver, + int32_t request_id, + uint32_t options, + const network::ResourceRequest& request, + mojo::PendingRemote<network::mojom::URLLoaderClient> client, + const net::MutableNetworkTrafficAnnotationTag& traffic_annotation); + + InProgressRequest(const InProgressRequest&) = delete; + InProgressRequest& operator=(const InProgressRequest&) = delete; + + ~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 net::HttpRequestHeaders& modified_cors_exempt_headers, + const absl::optional<GURL>& new_url) override; + + 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 OnReceiveEarlyHints(network::mojom::EarlyHintsPtr early_hints) override { + target_client_->OnReceiveEarlyHints(std::move(early_hints)); + } + void OnReceiveResponse(network::mojom::URLResponseHeadPtr head) override; + void OnReceiveRedirect(const net::RedirectInfo& redirect_info, + network::mojom::URLResponseHeadPtr 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(mojo_base::BigBuffer data) override { + target_client_->OnReceiveCachedMetadata(std::move(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. + const raw_ptr<ProxyingURLLoaderFactory> factory_; + + // Information about the current request. + GURL request_url_; + GURL response_url_; + GURL referrer_origin_; + net::HttpRequestHeaders headers_; + net::HttpRequestHeaders cors_exempt_headers_; + net::RedirectInfo redirect_info_; + const network::mojom::RequestDestination request_destination_; + const bool is_main_frame_; + const bool is_fetch_like_api_; + + base::OnceClosure destruction_callback_; + + // Messages received by |client_receiver_| are forwarded to |target_client_|. + mojo::Receiver<network::mojom::URLLoaderClient> client_receiver_{this}; + mojo::Remote<network::mojom::URLLoaderClient> target_client_; + + // Messages received by |loader_receiver_| are forwarded to |target_loader_|. + mojo::Receiver<network::mojom::URLLoader> loader_receiver_; + mojo::Remote<network::mojom::URLLoader> target_loader_; +}; + +class ProxyingURLLoaderFactory::InProgressRequest::ProxyRequestAdapter + : public ChromeRequestAdapter { + public: + // Does not take |modified_cors_exempt_headers| just because we don't have a + // use-case to modify it in this class now. + ProxyRequestAdapter(InProgressRequest* in_progress_request, + const net::HttpRequestHeaders& original_headers, + net::HttpRequestHeaders* modified_headers, + std::vector<std::string>* removed_headers) + : ChromeRequestAdapter(in_progress_request->request_url_, + original_headers, + modified_headers, + removed_headers), + in_progress_request_(in_progress_request) { + DCHECK(in_progress_request_); + } + + ProxyRequestAdapter(const ProxyRequestAdapter&) = delete; + ProxyRequestAdapter& operator=(const ProxyRequestAdapter&) = delete; + + ~ProxyRequestAdapter() override = default; + + content::WebContents::Getter GetWebContentsGetter() const override { + return in_progress_request_->factory_->web_contents_getter_; + } + + network::mojom::RequestDestination GetRequestDestination() const override { + return in_progress_request_->request_destination_; + } + + bool IsFetchLikeAPI() const override { + return in_progress_request_->is_fetch_like_api_; + } + + 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); + } + + private: + const raw_ptr<InProgressRequest> in_progress_request_; +}; + +class ProxyingURLLoaderFactory::InProgressRequest::ProxyResponseAdapter + : public ResponseAdapter { + public: + ProxyResponseAdapter(InProgressRequest* in_progress_request, + net::HttpResponseHeaders* headers) + : in_progress_request_(in_progress_request), headers_(headers) { + DCHECK(in_progress_request_); + DCHECK(headers_); + } + + ProxyResponseAdapter(const ProxyResponseAdapter&) = delete; + ProxyResponseAdapter& operator=(const ProxyResponseAdapter&) = delete; + + ~ProxyResponseAdapter() override = default; + + // signin::ResponseAdapter + content::WebContents::Getter 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_.DeprecatedGetOriginAsURL(); + } + + const net::HttpResponseHeaders* GetHeaders() const override { + return headers_; + } + + void RemoveHeader(const std::string& name) override { + headers_->RemoveHeader(name); + } + + base::SupportsUserData::Data* GetUserData(const void* key) const override { + return in_progress_request_->GetUserData(key); + } + + void SetUserData( + const void* key, + std::unique_ptr<base::SupportsUserData::Data> data) override { + in_progress_request_->SetUserData(key, std::move(data)); + } + + private: + const raw_ptr<InProgressRequest> in_progress_request_; + const raw_ptr<net::HttpResponseHeaders> headers_; +}; + +ProxyingURLLoaderFactory::InProgressRequest::InProgressRequest( + ProxyingURLLoaderFactory* factory, + mojo::PendingReceiver<network::mojom::URLLoader> loader_receiver, + int32_t request_id, + uint32_t options, + const network::ResourceRequest& request, + mojo::PendingRemote<network::mojom::URLLoaderClient> client, + const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) + : factory_(factory), + request_url_(request.url), + response_url_(request.url), + referrer_origin_(request.referrer.DeprecatedGetOriginAsURL()), + request_destination_(request.destination), + is_main_frame_(request.is_main_frame), + is_fetch_like_api_(request.is_fetch_like_api), + target_client_(std::move(client)), + loader_receiver_(this, std::move(loader_receiver)) { + mojo::PendingRemote<network::mojom::URLLoaderClient> proxy_client = + client_receiver_.BindNewPipeAndPassRemote(); + + 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( + target_loader_.BindNewPipeAndPassReceiver(), 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); + cors_exempt_headers_.CopyFrom(request.cors_exempt_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); + request_copy.cors_exempt_headers.RemoveHeader(name); + } + + factory_->target_factory_->CreateLoaderAndStart( + target_loader_.BindNewPipeAndPassReceiver(), request_id, options, + request_copy, std::move(proxy_client), traffic_annotation); + + headers_.Swap(&request_copy.headers); + cors_exempt_headers_.Swap(&request_copy.cors_exempt_headers); + } + + base::RepeatingClosure closure = base::BarrierClosure( + 2, base::BindOnce(&InProgressRequest::OnBindingsClosed, + base::Unretained(this))); + loader_receiver_.set_disconnect_handler(closure); + client_receiver_.set_disconnect_handler(closure); +} + +void ProxyingURLLoaderFactory::InProgressRequest::FollowRedirect( + const std::vector<std::string>& removed_headers_ext, + const net::HttpRequestHeaders& modified_headers_ext, + const net::HttpRequestHeaders& modified_cors_exempt_headers_ext, + const absl::optional<GURL>& opt_new_url) { + std::vector<std::string> removed_headers = removed_headers_ext; + net::HttpRequestHeaders modified_headers = modified_headers_ext; + net::HttpRequestHeaders modified_cors_exempt_headers = + modified_cors_exempt_headers_ext; + ProxyRequestAdapter adapter(this, headers_, &modified_headers, + &removed_headers); + factory_->delegate_->ProcessRequest(&adapter, redirect_info_.new_url); + + headers_.MergeFrom(modified_headers); + cors_exempt_headers_.MergeFrom(modified_cors_exempt_headers); + for (const std::string& name : removed_headers) { + headers_.RemoveHeader(name); + cors_exempt_headers_.RemoveHeader(name); + } + + target_loader_->FollowRedirect(removed_headers, modified_headers, + modified_cors_exempt_headers, opt_new_url); + + request_url_ = redirect_info_.new_url; + referrer_origin_ = + GURL(redirect_info_.new_referrer).DeprecatedGetOriginAsURL(); +} + +void ProxyingURLLoaderFactory::InProgressRequest::OnReceiveResponse( + network::mojom::URLResponseHeadPtr 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(std::move(head)); +} + +void ProxyingURLLoaderFactory::InProgressRequest::OnReceiveRedirect( + const net::RedirectInfo& redirect_info, + network::mojom::URLResponseHeadPtr 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, std::move(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::WebContents::Getter web_contents_getter, + mojo::PendingReceiver<network::mojom::URLLoaderFactory> loader_receiver, + mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory, + DisconnectCallback on_disconnect) { + DCHECK(proxy_receivers_.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_disconnect_handler(base::BindOnce( + &ProxyingURLLoaderFactory::OnTargetFactoryError, base::Unretained(this))); + + proxy_receivers_.Add(this, std::move(loader_receiver)); + proxy_receivers_.set_disconnect_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, + mojo::PendingReceiver<network::mojom::URLLoaderFactory>* factory_receiver) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + // Navigation requests are handled using signin::URLLoaderThrottle. + if (is_navigation) + return false; + + if (!render_frame_host) + 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) + // Most requests from guest web views are ignored. + if (HeaderModificationDelegateImpl::ShouldIgnoreGuestWebViewRequest( + web_contents)) { + return false; + } +#endif + + auto proxied_receiver = std::move(*factory_receiver); + // TODO(crbug.com/955171): Replace this with PendingRemote. + mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory_remote; + *factory_receiver = target_factory_remote.InitWithNewPipeAndPassReceiver(); + + auto web_contents_getter = + base::BindRepeating(&content::WebContents::FromFrameTreeNodeId, + render_frame_host->GetFrameTreeNodeId()); + + BrowserContextData::StartProxying(profile, std::move(web_contents_getter), + std::move(proxied_receiver), + std::move(target_factory_remote)); + return true; +} + +void ProxyingURLLoaderFactory::CreateLoaderAndStart( + mojo::PendingReceiver<network::mojom::URLLoader> loader_receiver, + int32_t request_id, + uint32_t options, + const network::ResourceRequest& request, + mojo::PendingRemote<network::mojom::URLLoaderClient> client, + const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) { + requests_.insert(std::make_unique<InProgressRequest>( + this, std::move(loader_receiver), request_id, options, request, + std::move(client), traffic_annotation)); +} + +void ProxyingURLLoaderFactory::Clone( + mojo::PendingReceiver<network::mojom::URLLoaderFactory> loader_receiver) { + proxy_receivers_.Add(this, std::move(loader_receiver)); +} + +void ProxyingURLLoaderFactory::OnTargetFactoryError() { + // Stop calls to CreateLoaderAndStart() when |target_factory_| is invalid. + target_factory_.reset(); + proxy_receivers_.Clear(); + + MaybeDestroySelf(); +} + +void ProxyingURLLoaderFactory::OnProxyBindingError() { + if (proxy_receivers_.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 |