blob: e5660f55808beea80b5f6a7ba9a3537a16431153 [file] [log] [blame]
// Copyright 2020 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 "weblayer/browser/signin_url_loader_throttle.h"
#include "base/task/post_task.h"
#include "components/content_settings/core/browser/cookie_settings.h"
#include "components/signin/core/browser/signin_header_helper.h"
#include "components/signin/public/base/account_consistency_method.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "google_apis/gaia/gaia_auth_util.h"
#include "net/base/url_util.h"
#include "third_party/blink/public/mojom/loader/resource_load_info.mojom.h"
#include "weblayer/browser/cookie_settings_factory.h"
#include "weblayer/browser/tab_impl.h"
#include "weblayer/public/google_accounts_delegate.h"
namespace weblayer {
const char kSignOutPath[] = "/SignOutOptions";
namespace {
constexpr char kWebLayerMirrorHeaderSource[] = "WebLayer";
GoogleAccountsDelegate* GetDelegate(
const content::WebContents::Getter& web_contents_getter) {
auto* web_contents = web_contents_getter.Run();
if (!web_contents)
return nullptr;
auto* tab = TabImpl::FromWebContents(web_contents);
if (!tab)
return nullptr;
return tab->google_accounts_delegate();
}
void ProcessMirrorHeader(content::WebContents::Getter web_contents_getter,
const signin::ManageAccountsParams& params) {
auto* delegate = GetDelegate(web_contents_getter);
if (delegate)
delegate->OnGoogleAccountsRequest(params);
}
void MaybeAddQueryParams(GURL* url) {
// Add manage=true to query parameters for sign out URLs to make sure we
// receive the Mirror response headers instead of the normal sign out page.
if (gaia::IsGaiaSignonRealm(url->GetOrigin()) &&
url->path_piece() == kSignOutPath) {
*url = net::AppendOrReplaceQueryParameter(*url, "manage", "true");
}
}
} // namespace
SigninURLLoaderThrottle::~SigninURLLoaderThrottle() = default;
// static
std::unique_ptr<SigninURLLoaderThrottle> SigninURLLoaderThrottle::Create(
content::BrowserContext* browser_context,
content::WebContents::Getter web_contents_getter) {
if (!GetDelegate(web_contents_getter))
return nullptr;
// Use base::WrapUnique + new because of the constructor is private.
return base::WrapUnique(new SigninURLLoaderThrottle(
browser_context, std::move(web_contents_getter)));
}
void SigninURLLoaderThrottle::WillStartRequest(
network::ResourceRequest* request,
bool* defer) {
GoogleAccountsDelegate* delegate = GetDelegate(web_contents_getter_);
if (!delegate)
return;
MaybeAddQueryParams(&request->url);
request_url_ = request->url;
is_main_frame_ =
static_cast<blink::mojom::ResourceType>(request->resource_type) ==
blink::mojom::ResourceType::kMainFrame;
net::HttpRequestHeaders modified_request_headers;
std::vector<std::string> to_be_removed_request_headers;
ProcessRequest(GURL(), &request->headers, &to_be_removed_request_headers,
&modified_request_headers);
signin::RequestAdapter adapter(request_url_, request->headers,
&modified_request_headers,
&to_be_removed_request_headers);
request_headers_.CopyFrom(request->headers);
}
void SigninURLLoaderThrottle::WillRedirectRequest(
net::RedirectInfo* redirect_info,
const network::mojom::URLResponseHead& response_head,
bool* defer,
std::vector<std::string>* headers_to_remove,
net::HttpRequestHeaders* modified_headers,
net::HttpRequestHeaders* modified_cors_exempt_request_headers) {
if (!GetDelegate(web_contents_getter_))
return;
MaybeAddQueryParams(&redirect_info->new_url);
ProcessRequest(redirect_info->new_url, &request_headers_, headers_to_remove,
modified_headers);
ProcessResponse(response_head.headers.get());
request_url_ = redirect_info->new_url;
}
void SigninURLLoaderThrottle::WillProcessResponse(
const GURL& response_url,
network::mojom::URLResponseHead* response_head,
bool* defer) {
if (!GetDelegate(web_contents_getter_))
return;
ProcessResponse(response_head->headers.get());
}
SigninURLLoaderThrottle::SigninURLLoaderThrottle(
content::BrowserContext* browser_context,
content::WebContents::Getter web_contents_getter)
: browser_context_(browser_context),
web_contents_getter_(std::move(web_contents_getter)) {}
void SigninURLLoaderThrottle::ProcessRequest(
const GURL& new_url,
net::HttpRequestHeaders* original_headers,
std::vector<std::string>* headers_to_remove,
net::HttpRequestHeaders* modified_headers) {
GoogleAccountsDelegate* delegate = GetDelegate(web_contents_getter_);
if (!delegate)
return;
signin::RequestAdapter request_adapter(request_url_, *original_headers,
modified_headers, headers_to_remove);
// Disable incognito and adding accounts for now. This shouldn't matter in
// practice though since we are skipping the /SignOutOptions page completely
// with the manage=true param.
//
// TODO(crbug.com/1134042): Check whether the child account status should also
// be sent in the Mirror request header from WebLayer.
signin::AppendOrRemoveMirrorRequestHeader(
&request_adapter, new_url, delegate->GetGaiaId(),
base::nullopt /* is_child_account */,
signin::AccountConsistencyMethod::kMirror,
CookieSettingsFactory::GetForBrowserContext(browser_context_).get(),
signin::PROFILE_MODE_INCOGNITO_DISABLED |
signin::PROFILE_MODE_ADD_ACCOUNT_DISABLED,
kWebLayerMirrorHeaderSource, true /* force_account_consistency */);
original_headers->MergeFrom(*modified_headers);
for (const std::string& name : *headers_to_remove)
original_headers->RemoveHeader(name);
}
void SigninURLLoaderThrottle::ProcessResponse(
const net::HttpResponseHeaders* headers) {
if (!gaia::IsGaiaSignonRealm(request_url_.GetOrigin()) || !is_main_frame_ ||
!headers) {
return;
}
std::string header_value;
if (!headers->GetNormalizedHeader(signin::kChromeManageAccountsHeader,
&header_value)) {
return;
}
signin::ManageAccountsParams params =
signin::BuildManageAccountsParams(header_value);
if (params.service_type == signin::GAIA_SERVICE_TYPE_NONE)
return;
// Only process one mirror header per request (multiple headers on the same
// redirect chain are ignored).
if (response_header_processed_) {
LOG(ERROR) << "Multiple X-Chrome-Manage-Accounts headers on a redirect "
<< "chain, ignoring";
return;
}
response_header_processed_ = true;
// Post a task even if we are already on the UI thread to avoid making any
// requests while processing a throttle event.
base::PostTask(
FROM_HERE, {content::BrowserThread::UI},
base::BindOnce(&ProcessMirrorHeader, web_contents_getter_, params));
}
} // namespace weblayer