blob: ea3e22bcd911ecc271f5d14e406e6797aefcc09e [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// 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_url_loader_throttle.h"
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/test/mock_callback.h"
#include "chrome/browser/signin/chrome_signin_helper.h"
#include "chrome/browser/signin/header_modification_delegate.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::ElementsAre;
using testing::Invoke;
using testing::Return;
using testing::_;
namespace signin {
namespace {
class MockDelegate : public HeaderModificationDelegate {
public:
MockDelegate() = default;
MockDelegate(const MockDelegate&) = delete;
MockDelegate& operator=(const MockDelegate&) = delete;
~MockDelegate() override = default;
MOCK_METHOD1(ShouldInterceptNavigation, bool(content::WebContents* contents));
MOCK_METHOD2(ProcessRequest,
void(ChromeRequestAdapter* request_adapter,
const GURL& redirect_url));
MOCK_METHOD2(ProcessResponse,
void(ResponseAdapter* response_adapter,
const GURL& redirect_url));
};
content::WebContents::Getter NullWebContentsGetter() {
return base::BindRepeating([]() -> content::WebContents* { return nullptr; });
}
} // namespace
TEST(ChromeSigninURLLoaderThrottleTest, NoIntercept) {
auto* delegate = new MockDelegate();
EXPECT_CALL(*delegate, ShouldInterceptNavigation(_)).WillOnce(Return(false));
EXPECT_FALSE(URLLoaderThrottle::MaybeCreate(base::WrapUnique(delegate),
NullWebContentsGetter()));
}
TEST(ChromeSigninURLLoaderThrottleTest, Intercept) {
auto* delegate = new MockDelegate();
EXPECT_CALL(*delegate, ShouldInterceptNavigation(_)).WillOnce(Return(true));
auto throttle = URLLoaderThrottle::MaybeCreate(base::WrapUnique(delegate),
NullWebContentsGetter());
ASSERT_TRUE(throttle);
// Phase 1: Start the request.
const GURL kTestURL("https://google.com/index.html");
const GURL kTestReferrer("https://chrome.com/referrer.html");
base::MockCallback<base::OnceClosure> destruction_callback;
EXPECT_CALL(*delegate, ProcessRequest(_, _))
.WillOnce(
Invoke([&](ChromeRequestAdapter* adapter, const GURL& redirect_url) {
EXPECT_EQ(kTestURL, adapter->GetUrl());
EXPECT_EQ(network::mojom::RequestDestination::kDocument,
adapter->GetRequestDestination());
EXPECT_TRUE(adapter->IsOutermostMainFrame());
EXPECT_EQ(kTestReferrer, adapter->GetReferrer());
EXPECT_TRUE(adapter->HasHeader("X-Request-1"));
adapter->RemoveRequestHeaderByName("X-Request-1");
EXPECT_FALSE(adapter->HasHeader("X-Request-1"));
adapter->SetExtraHeaderByName("X-Request-2", "Bar");
EXPECT_TRUE(adapter->HasHeader("X-Request-2"));
EXPECT_EQ(GURL(), redirect_url);
adapter->SetDestructionCallback(destruction_callback.Get());
}));
network::ResourceRequest request;
request.url = kTestURL;
request.referrer = kTestReferrer;
request.destination = network::mojom::RequestDestination::kDocument;
request.is_outermost_main_frame = true;
request.headers.SetHeader("X-Request-1", "Foo");
bool defer = false;
throttle->WillStartRequest(&request, &defer);
EXPECT_FALSE(request.headers.HasHeader("X-Request-1"));
EXPECT_THAT(request.headers.GetHeader("X-Request-2"),
testing::Optional(std::string("Bar")));
EXPECT_FALSE(defer);
testing::Mock::VerifyAndClearExpectations(delegate);
// Phase 2: Redirect the request.
const GURL kTestRedirectURL("https://youtube.com/index.html");
const void* const kResponseUserDataKey = &kResponseUserDataKey;
std::unique_ptr<base::SupportsUserData::Data> response_user_data =
std::make_unique<base::SupportsUserData::Data>();
base::SupportsUserData::Data* response_user_data_ptr =
response_user_data.get();
EXPECT_CALL(*delegate, ProcessResponse(_, _))
.WillOnce(Invoke([&](ResponseAdapter* adapter, const GURL& redirect_url) {
EXPECT_EQ(kTestURL, adapter->GetUrl());
EXPECT_TRUE(adapter->IsOutermostMainFrame());
adapter->SetUserData(kResponseUserDataKey,
std::move(response_user_data));
EXPECT_EQ(response_user_data_ptr,
adapter->GetUserData(kResponseUserDataKey));
const net::HttpResponseHeaders* headers = adapter->GetHeaders();
EXPECT_TRUE(headers->HasHeader("X-Response-1"));
EXPECT_TRUE(headers->HasHeader("X-Response-2"));
adapter->RemoveHeader("X-Response-2");
EXPECT_EQ(kTestRedirectURL, redirect_url);
}));
base::MockCallback<base::OnceClosure> ignored_destruction_callback;
EXPECT_CALL(*delegate, ProcessRequest(_, _))
.WillOnce(
Invoke([&](ChromeRequestAdapter* adapter, const GURL& redirect_url) {
EXPECT_EQ(network::mojom::RequestDestination::kDocument,
adapter->GetRequestDestination());
EXPECT_TRUE(adapter->IsOutermostMainFrame());
// Changes to the URL and referrer take effect after the redirect
// is followed.
EXPECT_EQ(kTestURL, adapter->GetUrl());
EXPECT_EQ(kTestReferrer, adapter->GetReferrer());
// X-Request-1 and X-Request-2 were modified in the previous call to
// ProcessRequest(). These changes should still be present.
EXPECT_FALSE(adapter->HasHeader("X-Request-1"));
EXPECT_TRUE(adapter->HasHeader("X-Request-2"));
adapter->RemoveRequestHeaderByName("X-Request-2");
EXPECT_FALSE(adapter->HasHeader("X-Request-2"));
adapter->SetExtraHeaderByName("X-Request-3", "Baz");
EXPECT_TRUE(adapter->HasHeader("X-Request-3"));
EXPECT_EQ(kTestRedirectURL, redirect_url);
adapter->SetDestructionCallback(ignored_destruction_callback.Get());
}));
net::RedirectInfo redirect_info;
redirect_info.new_url = kTestRedirectURL;
// An HTTPS to HTTPS redirect such as this wouldn't normally change the
// referrer but we do for testing purposes.
redirect_info.new_referrer = kTestURL.spec();
auto response_head = network::mojom::URLResponseHead::New();
response_head->headers = base::MakeRefCounted<net::HttpResponseHeaders>("");
response_head->headers->SetHeader("X-Response-1", "Foo");
response_head->headers->SetHeader("X-Response-2", "Bar");
std::vector<std::string> request_headers_to_remove;
net::HttpRequestHeaders modified_request_headers;
net::HttpRequestHeaders modified_cors_exempt_request_headers;
throttle->WillRedirectRequest(
&redirect_info, *response_head, &defer, &request_headers_to_remove,
&modified_request_headers, &modified_cors_exempt_request_headers);
EXPECT_FALSE(defer);
EXPECT_TRUE(response_head->headers->HasHeader("X-Response-1"));
EXPECT_FALSE(response_head->headers->HasHeader("X-Response-2"));
EXPECT_THAT(request_headers_to_remove, ElementsAre("X-Request-2"));
EXPECT_THAT(modified_request_headers.GetHeader("X-Request-3"),
testing::Optional(std::string("Baz")));
EXPECT_TRUE(modified_cors_exempt_request_headers.IsEmpty());
testing::Mock::VerifyAndClearExpectations(delegate);
// Phase 3: Complete the request.
EXPECT_CALL(*delegate, ProcessResponse(_, _))
.WillOnce(Invoke([&](ResponseAdapter* adapter, const GURL& redirect_url) {
EXPECT_EQ(kTestRedirectURL, adapter->GetUrl());
EXPECT_TRUE(adapter->IsOutermostMainFrame());
EXPECT_EQ(response_user_data_ptr,
adapter->GetUserData(kResponseUserDataKey));
const net::HttpResponseHeaders* headers = adapter->GetHeaders();
// This is a new response and so previous headers should not carry over.
EXPECT_FALSE(headers->HasHeader("X-Response-1"));
EXPECT_FALSE(headers->HasHeader("X-Response-2"));
EXPECT_TRUE(headers->HasHeader("X-Response-3"));
EXPECT_TRUE(headers->HasHeader("X-Response-4"));
adapter->RemoveHeader("X-Response-3");
EXPECT_EQ(GURL(), redirect_url);
}));
response_head = network::mojom::URLResponseHead::New();
response_head->headers = base::MakeRefCounted<net::HttpResponseHeaders>("");
response_head->headers->SetHeader("X-Response-3", "Foo");
response_head->headers->SetHeader("X-Response-4", "Bar");
throttle->WillProcessResponse(kTestRedirectURL, response_head.get(), &defer);
EXPECT_FALSE(response_head->headers->HasHeader("X-Response-3"));
EXPECT_TRUE(response_head->headers->HasHeader("X-Response-4"));
EXPECT_FALSE(defer);
EXPECT_CALL(destruction_callback, Run()).Times(1);
EXPECT_CALL(ignored_destruction_callback, Run()).Times(0);
throttle.reset();
}
TEST(ChromeSigninURLLoaderThrottleTest, InterceptSubFrame) {
auto* delegate = new MockDelegate();
EXPECT_CALL(*delegate, ShouldInterceptNavigation(_)).WillOnce(Return(true));
auto throttle = URLLoaderThrottle::MaybeCreate(base::WrapUnique(delegate),
NullWebContentsGetter());
ASSERT_TRUE(throttle);
EXPECT_CALL(*delegate, ProcessRequest(_, _))
.Times(2)
.WillRepeatedly(
[](ChromeRequestAdapter* adapter, const GURL& redirect_url) {
EXPECT_EQ(network::mojom::RequestDestination::kIframe,
adapter->GetRequestDestination());
EXPECT_FALSE(adapter->IsOutermostMainFrame());
});
network::ResourceRequest request;
request.url = GURL("https://google.com");
request.destination = network::mojom::RequestDestination::kIframe;
request.is_outermost_main_frame = false;
bool defer = false;
throttle->WillStartRequest(&request, &defer);
EXPECT_FALSE(defer);
EXPECT_CALL(*delegate, ProcessResponse(_, _))
.Times(2)
.WillRepeatedly(([](ResponseAdapter* adapter, const GURL& redirect_url) {
EXPECT_FALSE(adapter->IsOutermostMainFrame());
}));
net::RedirectInfo redirect_info;
redirect_info.new_url = GURL("https://youtube.com");
auto response_head = network::mojom::URLResponseHead::New();
std::vector<std::string> request_headers_to_remove;
net::HttpRequestHeaders modified_request_headers;
net::HttpRequestHeaders modified_cors_exempt_request_headers;
throttle->WillRedirectRequest(
&redirect_info, *response_head, &defer, &request_headers_to_remove,
&modified_request_headers, &modified_cors_exempt_request_headers);
EXPECT_FALSE(defer);
EXPECT_TRUE(request_headers_to_remove.empty());
EXPECT_TRUE(modified_request_headers.IsEmpty());
EXPECT_TRUE(modified_cors_exempt_request_headers.IsEmpty());
throttle->WillProcessResponse(GURL("https://youtube.com"),
response_head.get(), &defer);
EXPECT_FALSE(defer);
}
} // namespace signin