0

[2/4] Account Capabilities Fetcher

This CL adds a method for fetching account capabilities to
GaiaOAuthClient. Callers to this method will be added in follow-up CLs.

To call account capabilities API, a caller must provide an access token
with the https://www.googleapis.com/auth/account.capabilities scope as
well as a list of capabilities to fetch.

GaiaOAuthClient doesn't completely parse the response. This is done
one layer up, where AccountCapabilities definition is available.

Bug: 1213071
Change-Id: Ifa5513515a4faf5fe9f016a4a89c71412ad61008
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2940533
Reviewed-by: Ramin Halavati <rhalavati@chromium.org>
Reviewed-by: Mihai Sardarescu <msarda@chromium.org>
Commit-Queue: Alex Ilin <alexilin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#890721}
This commit is contained in:
Alex Ilin
2021-06-09 12:56:58 +00:00
committed by Chromium LUCI CQ
parent a9b5392e1a
commit 81ce2b504c
5 changed files with 198 additions and 51 deletions

@ -8,10 +8,12 @@
#include <utility>
#include "base/bind.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/json/json_reader.h"
#include "base/memory/weak_ptr.h"
#include "base/notreached.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/values.h"
#include "google_apis/gaia/gaia_auth_util.h"
@ -78,6 +80,11 @@ class GaiaOAuthClient::Core
void GetUserInfo(const std::string& oauth_access_token,
int max_retries,
Delegate* delegate);
void GetAccountCapabilities(
const std::string& oauth_access_token,
const std::vector<std::string>& capabilities_names,
int max_retries,
Delegate* delegate);
void GetTokenInfo(const std::string& qualifier,
const std::string& query,
int max_retries,
@ -97,6 +104,7 @@ class GaiaOAuthClient::Core
USER_EMAIL,
USER_ID,
USER_INFO,
ACCOUNT_CAPABILITIES,
};
~Core() {}
@ -349,6 +357,59 @@ void GaiaOAuthClient::Core::GetTokenInfo(const std::string& qualifier,
traffic_annotation);
}
void GaiaOAuthClient::Core::GetAccountCapabilities(
const std::string& oauth_access_token,
const std::vector<std::string>& capabilities_names,
int max_retries,
Delegate* delegate) {
DCHECK(!capabilities_names.empty());
std::string post_body = base::StrCat(
{"names=", net::EscapeUrlEncodedData(*capabilities_names.begin(), true)});
for (auto it = capabilities_names.begin() + 1; it != capabilities_names.end();
++it) {
base::StrAppend(&post_body,
{"&names=", net::EscapeUrlEncodedData(*it, true)});
}
std::string auth = base::StrCat({"Bearer ", oauth_access_token});
net::MutableNetworkTrafficAnnotationTag traffic_annotation(
net::DefineNetworkTrafficAnnotation(
"gaia_oauth_client_get_account_capabilities",
R"(
semantics {
sender: "OAuth 2.0 calls"
description:
"This request is used to fetch account capabilities. Capabilities "
"provide information about state and features of Gaia accounts."
trigger:
"AccountTrackerService fetches account capabilities soon after the "
"user signs in. Afterwards, AccountTrackerService periodically "
"triggers this request to keep account capabilities up to date for "
"existing accounts."
data:
"The OAuth 2.0 access token of the account and a predefined list "
"of capabilities to fetch."
destination: GOOGLE_OWNED_SERVICE
}
policy {
cookies_allowed: NO
setting:
"This feature cannot be disabled in settings, but if the user "
"signs out of Chrome, this request would not be made."
chrome_policy {
SigninAllowed {
SigninAllowed: false
}
}
})"));
MakeRequest(ACCOUNT_CAPABILITIES,
GURL(GaiaUrls::GetInstance()->account_capabilities_url()),
post_body, auth, max_retries, delegate, traffic_annotation);
}
void GaiaOAuthClient::Core::MakeRequest(
RequestType type,
const GURL& url,
@ -524,7 +585,12 @@ void GaiaOAuthClient::Core::HandleResponse(std::unique_ptr<std::string> body,
break;
}
default:
case ACCOUNT_CAPABILITIES: {
delegate_->OnGetAccountCapabilitiesResponse(std::move(response_dict));
break;
}
case NO_PENDING_REQUEST:
NOTREACHED();
}
}
@ -593,4 +659,13 @@ void GaiaOAuthClient::GetTokenHandleInfo(const std::string& token_handle,
delegate);
}
void GaiaOAuthClient::GetAccountCapabilities(
const std::string& oauth_access_token,
const std::vector<std::string>& capabilities_names,
int max_retries,
Delegate* delegate) {
return core_->GetAccountCapabilities(oauth_access_token, capabilities_names,
max_retries, delegate);
}
} // namespace gaia

@ -51,6 +51,8 @@ class GaiaOAuthClient {
// Invoked on a successful response to the GetTokenInfo request.
virtual void OnGetTokenInfoResponse(
std::unique_ptr<base::DictionaryValue> token_info) {}
virtual void OnGetAccountCapabilitiesResponse(
std::unique_ptr<base::Value> account_capabilities) {}
// Invoked when there is an OAuth error with one of the requests.
virtual void OnOAuthError() = 0;
// Invoked when there is a network error or upon receiving an invalid
@ -135,6 +137,17 @@ class GaiaOAuthClient {
int max_retries,
Delegate* delegate);
// Call the account capabilities API, returning a dictionary of response
// values. Only fetches values for capabilities listed in
// |capabilities_names|. The provided access token must have
// https://www.googleapis.com/auth/account.capabilities in its scopes. See
// |max_retries| docs above.
void GetAccountCapabilities(
const std::string& oauth_access_token,
const std::vector<std::string>& capabilities_names,
int max_retries,
Delegate* delegate);
private:
// The guts of the implementation live in this class.
class Core;

@ -162,10 +162,42 @@ const std::string kDummyTokenHandleInfoResult =
"{\"audience\": \"1234567890.apps.googleusercontent.com\","
"\"expires_in\":" + base::NumberToString(kTestExpiresIn) + "}";
const std::string kDummyAccountCapabilitiesResult =
"{\"accountCapabilities\": ["
"{\"name\": \"accountcapabilities/111\", \"booleanValue\": false},"
"{\"name\": \"accountcapabilities/222\", \"booleanValue\": true}"
"]}";
} // namespace
namespace gaia {
class MockGaiaOAuthClientDelegate : public gaia::GaiaOAuthClient::Delegate {
public:
MockGaiaOAuthClientDelegate() = default;
MockGaiaOAuthClientDelegate(const MockGaiaOAuthClientDelegate&) = delete;
MockGaiaOAuthClientDelegate& operator=(const MockGaiaOAuthClientDelegate&) =
delete;
MOCK_METHOD3(OnGetTokensResponse,
void(const std::string& refresh_token,
const std::string& access_token,
int expires_in_seconds));
MOCK_METHOD2(OnRefreshTokenResponse,
void(const std::string& access_token, int expires_in_seconds));
MOCK_METHOD1(OnGetUserEmailResponse, void(const std::string& user_email));
MOCK_METHOD1(OnGetUserIdResponse, void(const std::string& user_id));
MOCK_METHOD1(OnGetUserInfoResponse,
void(std::unique_ptr<base::DictionaryValue> user_info));
MOCK_METHOD1(OnGetTokenInfoResponse,
void(std::unique_ptr<base::DictionaryValue> token_info));
MOCK_METHOD1(OnGetAccountCapabilitiesResponse,
void(std::unique_ptr<base::Value> account_capabilities));
MOCK_METHOD0(OnOAuthError, void());
MOCK_METHOD1(OnNetworkError, void(int response_code));
};
class GaiaOAuthClientTest : public testing::Test {
protected:
GaiaOAuthClientTest()
@ -191,52 +223,25 @@ class GaiaOAuthClientTest : public testing::Test {
}
protected:
void TestAccountCapabilitiesUploadData(
const std::vector<std::string>& capabilities_names,
const std::string& expected_body) {
ResponseInjector injector(&url_loader_factory_);
injector.set_complete_immediately(false);
MockGaiaOAuthClientDelegate delegate;
GaiaOAuthClient auth(GetSharedURLLoaderFactory());
auth.GetAccountCapabilities("some_token", capabilities_names, 1, &delegate);
EXPECT_EQ(injector.GetUploadData(), expected_body);
}
base::test::TaskEnvironment task_environment_;
network::TestURLLoaderFactory url_loader_factory_;
OAuthClientInfo client_info_;
};
class MockGaiaOAuthClientDelegate : public gaia::GaiaOAuthClient::Delegate {
public:
MockGaiaOAuthClientDelegate() {}
~MockGaiaOAuthClientDelegate() override {}
MOCK_METHOD3(OnGetTokensResponse, void(const std::string& refresh_token,
const std::string& access_token,
int expires_in_seconds));
MOCK_METHOD2(OnRefreshTokenResponse, void(const std::string& access_token,
int expires_in_seconds));
MOCK_METHOD1(OnGetUserEmailResponse, void(const std::string& user_email));
MOCK_METHOD1(OnGetUserIdResponse, void(const std::string& user_id));
MOCK_METHOD0(OnOAuthError, void());
MOCK_METHOD1(OnNetworkError, void(int response_code));
// gMock doesn't like methods that take or return scoped_ptr. A
// work-around is to create a mock method that takes a raw ptr, and
// override the problematic method to call through to it.
// https://groups.google.com/a/chromium.org/d/msg/chromium-dev/01sDxsJ1OYw/I_S0xCBRF2oJ
MOCK_METHOD1(OnGetUserInfoResponsePtr,
void(const base::DictionaryValue* user_info));
void OnGetUserInfoResponse(
std::unique_ptr<base::DictionaryValue> user_info) override {
user_info_ = std::move(user_info);
OnGetUserInfoResponsePtr(user_info_.get());
}
MOCK_METHOD1(OnGetTokenInfoResponsePtr,
void(const base::DictionaryValue* token_info));
void OnGetTokenInfoResponse(
std::unique_ptr<base::DictionaryValue> token_info) override {
token_info_ = std::move(token_info);
OnGetTokenInfoResponsePtr(token_info_.get());
}
private:
std::unique_ptr<base::DictionaryValue> user_info_;
std::unique_ptr<base::DictionaryValue> token_info_;
DISALLOW_COPY_AND_ASSIGN(MockGaiaOAuthClientDelegate);
};
TEST_F(GaiaOAuthClientTest, NetworkFailure) {
int response_code = net::HTTP_INTERNAL_SERVER_ERROR;
@ -413,11 +418,13 @@ TEST_F(GaiaOAuthClientTest, GetUserId) {
}
TEST_F(GaiaOAuthClientTest, GetUserInfo) {
const base::DictionaryValue* captured_result;
std::unique_ptr<base::DictionaryValue> captured_result;
MockGaiaOAuthClientDelegate delegate;
EXPECT_CALL(delegate, OnGetUserInfoResponsePtr(_))
.WillOnce(SaveArg<0>(&captured_result));
EXPECT_CALL(delegate, OnGetUserInfoResponse(_))
.WillOnce([&](std::unique_ptr<base::DictionaryValue> result) {
captured_result = std::move(result);
});
ResponseInjector injector(&url_loader_factory_);
injector.set_results(kDummyFullUserInfoResult);
@ -433,15 +440,17 @@ TEST_F(GaiaOAuthClientTest, GetUserInfo) {
base::DictionaryValue* expected_result;
value->GetAsDictionary(&expected_result);
ASSERT_TRUE(expected_result->Equals(captured_result));
ASSERT_TRUE(expected_result->Equals(captured_result.get()));
}
TEST_F(GaiaOAuthClientTest, GetTokenInfo) {
const base::DictionaryValue* captured_result;
std::unique_ptr<base::DictionaryValue> captured_result;
MockGaiaOAuthClientDelegate delegate;
EXPECT_CALL(delegate, OnGetTokenInfoResponsePtr(_))
.WillOnce(SaveArg<0>(&captured_result));
EXPECT_CALL(delegate, OnGetTokenInfoResponse(_))
.WillOnce([&](std::unique_ptr<base::DictionaryValue> result) {
captured_result = std::move(result);
});
ResponseInjector injector(&url_loader_factory_);
injector.set_results(kDummyTokenInfoResult);
@ -456,11 +465,13 @@ TEST_F(GaiaOAuthClientTest, GetTokenInfo) {
}
TEST_F(GaiaOAuthClientTest, GetTokenHandleInfo) {
const base::DictionaryValue* captured_result;
std::unique_ptr<base::DictionaryValue> captured_result;
MockGaiaOAuthClientDelegate delegate;
EXPECT_CALL(delegate, OnGetTokenInfoResponsePtr(_))
.WillOnce(SaveArg<0>(&captured_result));
EXPECT_CALL(delegate, OnGetTokenInfoResponse(_))
.WillOnce([&](std::unique_ptr<base::DictionaryValue> result) {
captured_result = std::move(result);
});
ResponseInjector injector(&url_loader_factory_);
injector.set_results(kDummyTokenHandleInfoResult);
@ -474,4 +485,50 @@ TEST_F(GaiaOAuthClientTest, GetTokenHandleInfo) {
ASSERT_EQ("1234567890.apps.googleusercontent.com", audience);
}
TEST_F(GaiaOAuthClientTest, GetAccountCapabilities) {
std::unique_ptr<base::Value> captured_result;
MockGaiaOAuthClientDelegate delegate;
EXPECT_CALL(delegate, OnGetAccountCapabilitiesResponse(_))
.WillOnce([&](std::unique_ptr<base::Value> result) {
captured_result = std::move(result);
});
ResponseInjector injector(&url_loader_factory_);
injector.set_results(kDummyAccountCapabilitiesResult);
injector.set_complete_immediately(false);
GaiaOAuthClient auth(GetSharedURLLoaderFactory());
auth.GetAccountCapabilities("some_token",
{"capability1", "capability2", "capability3"}, 1,
&delegate);
EXPECT_EQ(injector.GetUploadData(),
"names=capability1&names=capability2&names=capability3");
injector.Finish();
FlushNetwork();
auto capabilities =
captured_result->FindListKey("accountCapabilities")->GetList();
ASSERT_EQ(capabilities.size(), 2U);
EXPECT_EQ(*capabilities[0].FindStringKey("name"), "accountcapabilities/111");
EXPECT_FALSE(*capabilities[0].FindBoolKey("booleanValue"));
EXPECT_EQ(*capabilities[1].FindStringKey("name"), "accountcapabilities/222");
EXPECT_TRUE(*capabilities[1].FindBoolKey("booleanValue"));
}
TEST_F(GaiaOAuthClientTest,
GetAccountCapabilities_UploadData_OneCapabilityName) {
TestAccountCapabilitiesUploadData({"capability"},
/*expected_body=*/"names=capability");
}
TEST_F(GaiaOAuthClientTest,
GetAccountCapabilities_UploadData_MultipleCapabilityNames) {
TestAccountCapabilitiesUploadData(
{"capability1", "capability2", "capability3"},
/*expected_body=*/
"names=capability1&names=capability2&names=capability3");
}
} // namespace gaia

@ -141,6 +141,7 @@ Refer to README.md for content description and update process.
<item id="gaia_auth_revoke_token" added_in_milestone="62" hash_code="133982351" type="0" content_hash_code="96665330" os_list="linux,windows" file_path="google_apis/gaia/gaia_auth_fetcher.cc"/>
<item id="gaia_cookie_manager_external_cc_result" added_in_milestone="62" hash_code="4300475" type="0" content_hash_code="31188375" os_list="linux,windows" file_path="components/signin/internal/identity_manager/gaia_cookie_manager_service.cc"/>
<item id="gaia_create_reauth_proof_token_for_parent" added_in_milestone="80" hash_code="67750043" type="0" content_hash_code="103500636" os_list="linux,windows" file_path="google_apis/gaia/gaia_auth_fetcher.cc"/>
<item id="gaia_oauth_client_get_account_capabilities" added_in_milestone="93" hash_code="87437888" type="0" content_hash_code="51145869" os_list="linux,windows" file_path="google_apis/gaia/gaia_oauth_client.cc"/>
<item id="gaia_oauth_client_get_token_info" added_in_milestone="62" hash_code="32585152" type="0" content_hash_code="128143346" os_list="linux,windows" file_path="google_apis/gaia/gaia_oauth_client.cc"/>
<item id="gaia_oauth_client_get_tokens" added_in_milestone="62" hash_code="5637379" type="0" content_hash_code="12099176" os_list="linux,windows" file_path="google_apis/gaia/gaia_oauth_client.cc"/>
<item id="gaia_oauth_client_get_user_info" added_in_milestone="62" hash_code="83476155" type="0" content_hash_code="35159007" os_list="linux,windows" file_path="google_apis/gaia/gaia_oauth_client.cc"/>

@ -250,6 +250,7 @@ hidden="true" so that these annotations don't show up in the document.
<traffic_annotation unique_id="gaia_auth_multilogin"/>
<traffic_annotation unique_id="gaia_cookie_manager_external_cc_result"/>
<traffic_annotation unique_id="gaia_oauth_client_get_token_info"/>
<traffic_annotation unique_id="gaia_oauth_client_get_account_capabilities"/>
<traffic_annotation unique_id="gaia_oauth_client_get_tokens"/>
<traffic_annotation unique_id="gaia_oauth_client_get_user_info"/>
<traffic_annotation unique_id="gaia_oauth_client_refresh_token"/>