Commit 84268ca9 authored by Mihai Sardarescu's avatar Mihai Sardarescu Committed by Commit Bot

Make GaiaAuthFercherIOSBridge an interface.

There is a single implementation of a GaiaAuthFercherIOSBridge, so the
code is more readable if all the logic in this bridge lives only in
the implementation class.

Bug: 1101748
Change-Id: Ie7aaebb3f32323dd3310f273cda06d1225e3550d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2368553
Commit-Queue: Mihai Sardarescu <msarda@chromium.org>
Reviewed-by: default avatarNohemi Fernandez <fernandex@chromium.org>
Cr-Commit-Position: refs/heads/master@{#800974}
parent e0bb6d4e
......@@ -16,16 +16,7 @@
class GURL;
@class NSURLRequest;
namespace web {
class BrowserState;
}
// Specialization of GaiaAuthFetcher on iOS.
//
// Authenticate a user against the Google Accounts ClientLogin API
// with various capabilities and return results to a GaiaAuthConsumer.
// This class is an interface, and FetchPendingRequest() and Cancel() have to
// be implemented using native APIs in a subclass.
// Interface for fetching Gaia auth requests that include the cookies on iOS.
class GaiaAuthFetcherIOSBridge {
public:
// Delegate class receive notification whent the request is done.
......@@ -44,8 +35,7 @@ class GaiaAuthFetcherIOSBridge {
};
// Initializes the instance.
GaiaAuthFetcherIOSBridge(GaiaAuthFetcherIOSBridgeDelegate* delegate,
web::BrowserState* browser_state);
GaiaAuthFetcherIOSBridge(GaiaAuthFetcherIOSBridgeDelegate* delegate);
virtual ~GaiaAuthFetcherIOSBridge();
// Starts a network fetch.
......@@ -53,77 +43,27 @@ class GaiaAuthFetcherIOSBridge {
// * |headers| are the HTTP headers to add to the request.
// * |body| is the HTTP body to add to the request. If not empty, the fetch
// will be a POST request.
void Fetch(const GURL& url,
const std::string& headers,
const std::string& body,
bool should_use_xml_http_request);
//
// Implementations are expected to call
// GaiaAuthFetcherIOSBridgeDelegate::OnFetchComplete() when the fetch
// operation is finished.
virtual void Fetch(const GURL& url,
const std::string& headers,
const std::string& body,
bool should_use_xml_http_request) = 0;
// Cancels the current fetch.
//
// Implementations are expected to call
// GaiaAuthFetcherIOSBridgeDelegate::OnFetchComplete() with error
// |net::ERR_ABORTED|.
virtual void Cancel() = 0;
// Informs the bridge of the success of the URL fetch.
// * |data| is the body of the HTTP response.
// * |response_code| is the response code.
// URLFetchSuccess and URLFetchFailure are no-op if one of them was already
// called.
void OnURLFetchSuccess(const std::string& data, int response_code);
// Informs the bridge of the failure of the URL fetch.
// * |is_cancelled| whether the fetch failed because it was cancelled.
// URLFetchSuccess and URLFetchFailure are no-op if one of them was already
// called.
void OnURLFetchFailure(int error, int response_code);
// Returns the current browser state.
web::BrowserState* GetBrowserState() const;
protected:
// Fetches the pending request if it exists. The subclass needs to update the
// cookie store for each redirect and call either URLFetchSuccess() or
// URLFetchFailure().
virtual void FetchPendingRequest() = 0;
// A network request that needs to be fetched.
struct Request {
Request();
Request(const GURL& url,
const std::string& headers,
const std::string& body,
bool should_use_xml_http_request);
// Whether the request is pending (i.e. awaiting to be processed or
// currently being processed).
bool pending;
// URL to fetch.
GURL url;
// HTTP headers to add to the request.
std::string headers;
// HTTP body to add to the request.
std::string body;
// Whether XmlHTTPRequest should be injected in JS instead of using
// WKWebView directly.
bool should_use_xml_http_request;
};
// Returns a |request_| that contains the url, the headers and the body
// received in the constructor of this instance.
const Request& GetRequest() const;
// Creates a NSURLRequest with the url, the headers and the body received in
// the constructor of this instance. The request is a GET if |body| is empty
// and a POST otherwise.
NSURLRequest* GetNSURLRequest() const;
GaiaAuthFetcherIOSBridgeDelegate* delegate() { return delegate_; }
private:
// Finishes the pending request and cleans up its associated state. Returns
// the URL of the request.
GURL FinishPendingRequest();
// Delegate.
GaiaAuthFetcherIOSBridgeDelegate* delegate_;
// Browser state associated with the bridge.
web::BrowserState* browser_state_;
// Request currently processed by the bridge.
Request request_;
DISALLOW_COPY_AND_ASSIGN(GaiaAuthFetcherIOSBridge);
};
......
......@@ -22,88 +22,10 @@ GaiaAuthFetcherIOSBridge::GaiaAuthFetcherIOSBridgeDelegate::
GaiaAuthFetcherIOSBridge::GaiaAuthFetcherIOSBridgeDelegate::
~GaiaAuthFetcherIOSBridgeDelegate() {}
#pragma mark - GaiaAuthFetcherIOSBridge::Request
GaiaAuthFetcherIOSBridge::Request::Request()
: pending(false), should_use_xml_http_request(false) {}
GaiaAuthFetcherIOSBridge::Request::Request(const GURL& request_url,
const std::string& request_headers,
const std::string& request_body,
bool should_use_xml_http_request)
: pending(true),
url(request_url),
headers(request_headers),
body(request_body),
should_use_xml_http_request(should_use_xml_http_request) {}
#pragma mark - GaiaAuthFetcherIOSBridge
GaiaAuthFetcherIOSBridge::GaiaAuthFetcherIOSBridge(
GaiaAuthFetcherIOSBridgeDelegate* delegate,
web::BrowserState* browser_state)
: delegate_(delegate), browser_state_(browser_state) {}
GaiaAuthFetcherIOSBridgeDelegate* delegate)
: delegate_(delegate) {}
GaiaAuthFetcherIOSBridge::~GaiaAuthFetcherIOSBridge() {}
void GaiaAuthFetcherIOSBridge::Fetch(const GURL& url,
const std::string& headers,
const std::string& body,
bool should_use_xml_http_request) {
request_ = Request(url, headers, body, should_use_xml_http_request);
FetchPendingRequest();
}
void GaiaAuthFetcherIOSBridge::OnURLFetchSuccess(const std::string& data,
int response_code) {
if (!request_.pending) {
return;
}
GURL url = FinishPendingRequest();
delegate_->OnFetchComplete(url, data, net::OK, response_code);
}
void GaiaAuthFetcherIOSBridge::OnURLFetchFailure(int error, int response_code) {
if (!request_.pending) {
return;
}
GURL url = FinishPendingRequest();
delegate_->OnFetchComplete(url, std::string(), static_cast<net::Error>(error),
response_code);
}
web::BrowserState* GaiaAuthFetcherIOSBridge::GetBrowserState() const {
return browser_state_;
}
const GaiaAuthFetcherIOSBridge::Request& GaiaAuthFetcherIOSBridge::GetRequest()
const {
return request_;
}
NSURLRequest* GaiaAuthFetcherIOSBridge::GetNSURLRequest() const {
NSMutableURLRequest* request = [[NSMutableURLRequest alloc]
initWithURL:net::NSURLWithGURL(request_.url)];
net::HttpRequestHeaders request_headers;
request_headers.AddHeadersFromString(request_.headers);
for (net::HttpRequestHeaders::Iterator it(request_headers); it.GetNext();) {
[request setValue:base::SysUTF8ToNSString(it.value())
forHTTPHeaderField:base::SysUTF8ToNSString(it.name())];
}
if (!request_.body.empty()) {
NSData* post_data = [base::SysUTF8ToNSString(request_.body)
dataUsingEncoding:NSUTF8StringEncoding];
[request setHTTPBody:post_data];
[request setHTTPMethod:@"POST"];
DCHECK(![[request allHTTPHeaderFields] objectForKey:@"Content-Type"]);
[request setValue:@"application/x-www-form-urlencoded"
forHTTPHeaderField:@"Content-Type"];
}
return request;
}
GURL GaiaAuthFetcherIOSBridge::FinishPendingRequest() {
GURL url = request_.url;
request_ = Request();
return url;
}
......@@ -17,6 +17,10 @@ class GaiaAuthFetcherIOSNSURLSessionBridge;
@class NSHTTPURLResponse;
@class NSURLSession;
namespace web {
class BrowserState;
}
// Specialization of GaiaAuthFetcher on iOS, using NSURLSession to send
// requests.
class GaiaAuthFetcherIOSNSURLSessionBridge : public GaiaAuthFetcherIOSBridge {
......@@ -26,17 +30,65 @@ class GaiaAuthFetcherIOSNSURLSessionBridge : public GaiaAuthFetcherIOSBridge {
web::BrowserState* browser_state);
~GaiaAuthFetcherIOSNSURLSessionBridge() override;
// GaiaAuthFetcherIOSBridge.
// GaiaAuthFetcherIOSBridge:
void Fetch(const GURL& url,
const std::string& headers,
const std::string& body,
bool should_use_xml_http_request) override;
void Cancel() override;
// Informs the bridge of the success of the URL fetch.
// * |data| is the body of the HTTP response.
// * |response_code| is the response code.
// URLFetchSuccess and URLFetchFailure are no-op if one of them was already
// called.
void OnURLFetchSuccess(const std::string& data, int response_code);
// Informs the bridge of the failure of the URL fetch.
// * |is_cancelled| whether the fetch failed because it was cancelled.
// URLFetchSuccess and URLFetchFailure are no-op if one of them was already
// called.
void OnURLFetchFailure(int error, int response_code);
// Set cookies from |response| in SystemCookieStore asynchronously.
void SetCanonicalCookiesFromResponse(NSHTTPURLResponse* response);
private:
// A network request that needs to be fetched.
struct Request {
Request();
Request(const GURL& url,
const std::string& headers,
const std::string& body,
bool should_use_xml_http_request);
// Whether the request is pending (i.e. awaiting to be processed or
// currently being processed).
bool pending;
// URL to fetch.
GURL url;
// HTTP headers to add to the request.
std::string headers;
// HTTP body to add to the request.
std::string body;
// Whether XmlHTTPRequest should be injected in JS instead of using
// WKWebView directly.
bool should_use_xml_http_request;
};
friend class GaiaAuthFetcherIOSNSURLSessionBridgeTest;
// GaiaAuthFetcherIOSBridge.
void FetchPendingRequest() override;
// Creates a NSURLRequest with the url, the headers and the body received in
// the constructor of this instance. The request is a GET if |body| is empty
// and a POST otherwise.
NSURLRequest* GetNSURLRequest() const;
// Fetches the pending request if it exists. Updates the cookie store for
// each redirect and call either URLFetchSuccess() or URLFetchFailure().
void FetchPendingRequest();
// Finishes the pending request and cleans up its associated state. Returns
// the URL of the request.
GURL FinishPendingRequest();
// Starts the NSURLRequest with the cookie list.
void FetchPendingRequestWithCookies(
......@@ -46,10 +98,18 @@ class GaiaAuthFetcherIOSNSURLSessionBridge : public GaiaAuthFetcherIOSBridge {
virtual NSURLSession* CreateNSURLSession(
id<NSURLSessionTaskDelegate> url_session_delegate);
// Browser state associated with the bridge.
web::BrowserState* browser_state_;
// Request currently processed by the bridge.
Request request_;
// Fetcher which makes requests to Gaia with NSURLSession.
GaiaAuthFetcherIOSURLSessionDelegate* url_session_delegate_;
// Session to send the NSURLRequest.
NSURLSession* url_session_;
// Task to send the NSURLRequest.
NSURLSessionDataTask* url_session_data_task_;
......
......@@ -19,6 +19,24 @@
#error "This file requires ARC support."
#endif
#pragma mark - GaiaAuthFetcherIOSNSURLSessionBridge::Request
GaiaAuthFetcherIOSNSURLSessionBridge::Request::Request()
: pending(false), should_use_xml_http_request(false) {}
GaiaAuthFetcherIOSNSURLSessionBridge::Request::Request(
const GURL& request_url,
const std::string& request_headers,
const std::string& request_body,
bool should_use_xml_http_request)
: pending(true),
url(request_url),
headers(request_headers),
body(request_body),
should_use_xml_http_request(should_use_xml_http_request) {}
#pragma mark - GaiaAuthFetcherIOSURLSessionDelegate
@interface GaiaAuthFetcherIOSURLSessionDelegate
: NSObject <NSURLSessionTaskDelegate>
......@@ -80,7 +98,7 @@
GaiaAuthFetcherIOSNSURLSessionBridge::GaiaAuthFetcherIOSNSURLSessionBridge(
GaiaAuthFetcherIOSBridge::GaiaAuthFetcherIOSBridgeDelegate* delegate,
web::BrowserState* browser_state)
: GaiaAuthFetcherIOSBridge(delegate, browser_state) {
: GaiaAuthFetcherIOSBridge(delegate), browser_state_(browser_state) {
url_session_delegate_ = [[GaiaAuthFetcherIOSURLSessionDelegate alloc] init];
url_session_delegate_.bridge = this;
}
......@@ -89,15 +107,22 @@ GaiaAuthFetcherIOSNSURLSessionBridge::~GaiaAuthFetcherIOSNSURLSessionBridge() {
url_session_delegate_.bridge = nullptr;
}
void GaiaAuthFetcherIOSNSURLSessionBridge::FetchPendingRequest() {
void GaiaAuthFetcherIOSNSURLSessionBridge::Fetch(
const GURL& url,
const std::string& headers,
const std::string& body,
bool should_use_xml_http_request) {
DCHECK(!request_.pending);
request_ = Request(url, headers, body, should_use_xml_http_request);
network::mojom::CookieManager* cookie_manager =
GetBrowserState()->GetCookieManager();
browser_state_->GetCookieManager();
net::CookieOptions options;
options.set_include_httponly();
options.set_same_site_cookie_context(
net::CookieOptions::SameSiteCookieContext::MakeInclusive());
cookie_manager->GetCookieList(
GetRequest().url, options,
request_.url, options,
base::BindOnce(
&GaiaAuthFetcherIOSNSURLSessionBridge::FetchPendingRequestWithCookies,
base::Unretained(this)));
......@@ -109,13 +134,56 @@ void GaiaAuthFetcherIOSNSURLSessionBridge::Cancel() {
OnURLFetchFailure(net::ERR_ABORTED, 0);
}
void GaiaAuthFetcherIOSNSURLSessionBridge::OnURLFetchSuccess(
const std::string& data,
int response_code) {
if (!request_.pending) {
return;
}
GURL url = FinishPendingRequest();
delegate()->OnFetchComplete(url, data, net::OK, response_code);
}
void GaiaAuthFetcherIOSNSURLSessionBridge::OnURLFetchFailure(
int error,
int response_code) {
if (!request_.pending) {
return;
}
GURL url = FinishPendingRequest();
delegate()->OnFetchComplete(url, std::string(),
static_cast<net::Error>(error), response_code);
}
NSURLRequest* GaiaAuthFetcherIOSNSURLSessionBridge::GetNSURLRequest() const {
DCHECK(request_.pending);
NSMutableURLRequest* request = [[NSMutableURLRequest alloc]
initWithURL:net::NSURLWithGURL(request_.url)];
net::HttpRequestHeaders request_headers;
request_headers.AddHeadersFromString(request_.headers);
for (net::HttpRequestHeaders::Iterator it(request_headers); it.GetNext();) {
[request setValue:base::SysUTF8ToNSString(it.value())
forHTTPHeaderField:base::SysUTF8ToNSString(it.name())];
}
if (!request_.body.empty()) {
NSData* post_data = [base::SysUTF8ToNSString(request_.body)
dataUsingEncoding:NSUTF8StringEncoding];
[request setHTTPBody:post_data];
[request setHTTPMethod:@"POST"];
DCHECK(![[request allHTTPHeaderFields] objectForKey:@"Content-Type"]);
[request setValue:@"application/x-www-form-urlencoded"
forHTTPHeaderField:@"Content-Type"];
}
return request;
}
void GaiaAuthFetcherIOSNSURLSessionBridge::SetCanonicalCookiesFromResponse(
NSHTTPURLResponse* response) {
NSArray* cookies =
[NSHTTPCookie cookiesWithResponseHeaderFields:response.allHeaderFields
forURL:response.URL];
network::mojom::CookieManager* cookie_manager =
GetBrowserState()->GetCookieManager();
browser_state_->GetCookieManager();
for (NSHTTPCookie* cookie : cookies) {
net::CookieOptions options;
options.set_include_httponly();
......@@ -164,6 +232,13 @@ void GaiaAuthFetcherIOSNSURLSessionBridge::FetchPendingRequestWithCookies(
[url_session_data_task_ resume];
}
GURL GaiaAuthFetcherIOSNSURLSessionBridge::FinishPendingRequest() {
DCHECK(request_.pending);
GURL url = request_.url;
request_ = Request();
return url;
}
NSURLSession* GaiaAuthFetcherIOSNSURLSessionBridge::CreateNSURLSession(
id<NSURLSessionTaskDelegate> url_session_delegate) {
NSURLSessionConfiguration* session_configuration =
......
......@@ -24,15 +24,54 @@
namespace {
class MockGaiaAuthFetcherIOSBridge : public GaiaAuthFetcherIOSBridge {
class FakeGaiaAuthFetcherIOSBridge : public GaiaAuthFetcherIOSBridge {
public:
MockGaiaAuthFetcherIOSBridge(
GaiaAuthFetcherIOSBridge::GaiaAuthFetcherIOSBridgeDelegate* delegate,
web::BrowserState* browser_state)
: GaiaAuthFetcherIOSBridge(delegate, browser_state) {}
FakeGaiaAuthFetcherIOSBridge(
GaiaAuthFetcherIOSBridge::GaiaAuthFetcherIOSBridgeDelegate* delegate)
: GaiaAuthFetcherIOSBridge(delegate) {}
~FakeGaiaAuthFetcherIOSBridge() override {}
void Fetch(const GURL& url,
const std::string& headers,
const std::string& body,
bool should_use_xml_http_request) override {
fetch_called_ = true;
url_ = url;
}
void Cancel() override { cancel_called_ = true; }
void NotifyDelegateFetchSuccess(const std::string& data) {
EXPECT_TRUE(fetch_called_);
const int kSuccessResponseCode = 200;
delegate()->OnFetchComplete(url_, data, net::Error::OK,
kSuccessResponseCode);
fetch_called_ = false;
}
void NotifyDelegateFetchError(net::Error net_error) {
EXPECT_TRUE(fetch_called_);
const int kSomeErrorResponseCode = 500;
delegate()->OnFetchComplete(url_, "", net_error, kSomeErrorResponseCode);
fetch_called_ = false;
}
void NotifyDelegateFetchAborted() {
EXPECT_TRUE(cancel_called_);
const int kIgnoredResponseCode = 0;
delegate()->OnFetchComplete(url_, "", net::ERR_ABORTED,
kIgnoredResponseCode);
fetch_called_ = false;
cancel_called_ = false;
}
bool fetch_called() const { return fetch_called_; }
bool cancel_called() const { return cancel_called_; }
MOCK_METHOD0(Cancel, void());
MOCK_METHOD0(FetchPendingRequest, void());
private:
bool fetch_called_ = false;
bool cancel_called_ = false;
GURL url_;
};
class MockGaiaConsumer : public GaiaAuthConsumer {
......@@ -56,16 +95,16 @@ class GaiaAuthFetcherIOSTest : public PlatformTest {
gaia_auth_fetcher_.reset(new GaiaAuthFetcherIOS(
&consumer_, gaia::GaiaSource::kChrome,
test_url_loader_factory_.GetSafeWeakWrapper(), browser_state_.get()));
gaia_auth_fetcher_->bridge_.reset(new MockGaiaAuthFetcherIOSBridge(
gaia_auth_fetcher_.get(), browser_state_.get()));
gaia_auth_fetcher_->bridge_.reset(
new FakeGaiaAuthFetcherIOSBridge(gaia_auth_fetcher_.get()));
}
~GaiaAuthFetcherIOSTest() override {
gaia_auth_fetcher_.reset();
}
MockGaiaAuthFetcherIOSBridge* GetBridge() {
return static_cast<MockGaiaAuthFetcherIOSBridge*>(
FakeGaiaAuthFetcherIOSBridge* GetBridge() {
return static_cast<FakeGaiaAuthFetcherIOSBridge*>(
gaia_auth_fetcher_->bridge_.get());
}
......@@ -79,45 +118,39 @@ class GaiaAuthFetcherIOSTest : public PlatformTest {
// Tests that the cancel mechanism works properly by cancelling an OAuthLogin
// request and controlling that the consumer is properly called.
TEST_F(GaiaAuthFetcherIOSTest, StartOAuthLoginCancelled) {
MockGaiaAuthFetcherIOSBridge* bridge = GetBridge();
EXPECT_CALL(*bridge, FetchPendingRequest());
gaia_auth_fetcher_->StartOAuthLogin("fake_token", "gaia");
EXPECT_TRUE(GetBridge()->fetch_called());
gaia_auth_fetcher_->CancelRequest();
EXPECT_TRUE(GetBridge()->cancel_called());
GoogleServiceAuthError expected_error =
GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED);
EXPECT_CALL(consumer_, OnClientLoginFailure(expected_error));
EXPECT_CALL(*bridge, Cancel()).WillOnce([&bridge]() {
bridge->OnURLFetchFailure(net::ERR_ABORTED, 0);
});
gaia_auth_fetcher_->CancelRequest();
GetBridge()->NotifyDelegateFetchAborted();
}
// Tests that the successful case works properly by starting a MergeSession
// request, making it succeed and controlling that the consumer is properly
// called.
TEST_F(GaiaAuthFetcherIOSTest, StartMergeSession) {
MockGaiaAuthFetcherIOSBridge* bridge = GetBridge();
gaia_auth_fetcher_->StartMergeSession("uber_token", "");
EXPECT_TRUE(GetBridge()->fetch_called());
EXPECT_CALL(*bridge, FetchPendingRequest()).WillOnce([&bridge]() {
bridge->OnURLFetchSuccess("data", 200);
});
EXPECT_CALL(consumer_, OnMergeSessionSuccess("data"));
gaia_auth_fetcher_->StartMergeSession("uber_token", "");
GetBridge()->NotifyDelegateFetchSuccess("data");
}
// Tests that the failure case works properly by starting a LogOut request,
// making it fail, and controlling that the consumer is properly called.
TEST_F(GaiaAuthFetcherIOSTest, StartLogOutError) {
MockGaiaAuthFetcherIOSBridge* bridge = GetBridge();
gaia_auth_fetcher_->StartLogOut();
EXPECT_TRUE(GetBridge()->fetch_called());
GoogleServiceAuthError expected_error =
GoogleServiceAuthError(GoogleServiceAuthError::CONNECTION_FAILED);
GoogleServiceAuthError::FromConnectionError(net::Error::ERR_FAILED);
EXPECT_CALL(consumer_, OnLogOutFailure(expected_error));
EXPECT_CALL(*bridge, FetchPendingRequest()).WillOnce([&bridge]() {
bridge->OnURLFetchFailure(net::ERR_FAILED, 500);
});
gaia_auth_fetcher_->StartLogOut();
GetBridge()->NotifyDelegateFetchError(net::Error::ERR_FAILED);
}
// Tests that requests that do not require cookies are using the original
......@@ -135,5 +168,7 @@ TEST_F(GaiaAuthFetcherIOSTest, StartGetCheckConnectionInfo) {
data);
gaia_auth_fetcher_->StartGetCheckConnectionInfo();
EXPECT_FALSE(GetBridge()->fetch_called());
base::RunLoop().RunUntilIdle();
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment