Commit 662ae897 authored by Stephane Zermatten's avatar Stephane Zermatten Committed by Commit Bot

[Autofill Assistant] Add optional authentication.

With this change, if the user is logged into Chrome,
and unless authentication is disabled with
  --autofill-assistant-auth=false
the service will send out oauth credentials with the request.

This is one step towards making only authenticated calls.

Bug: 806868
Change-Id: I504a06af842869739ba44230d57f2a58259a480c
Reviewed-on: https://chromium-review.googlesource.com/c/1275890
Commit-Queue: Stephane Zermatten <szermatt@chromium.org>
Reviewed-by: default avatarMathias Carlen <mcarlen@chromium.org>
Reviewed-by: default avatarColin Blundell <blundell@chromium.org>
Cr-Commit-Position: refs/heads/master@{#602354}
parent 47546c89
......@@ -14,7 +14,9 @@
#include "base/command_line.h"
#include "chrome/browser/android/chrome_feature_list.h"
#include "chrome/browser/autofill/personal_data_manager_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/common/channel_info.h"
#include "components/autofill_assistant/browser/controller.h"
#include "components/variations/variations_associated_data.h"
......@@ -279,6 +281,24 @@ std::string UiControllerAndroid::GetApiKey() {
return api_key;
}
identity::IdentityManager*
UiControllerAndroid::GetIdentityManagerForPrimaryAccount() {
Profile* profile = ProfileManager::GetActiveUserProfile();
if (!profile) {
DLOG(ERROR) << "No active user profile. Cannot authenticate.";
return nullptr;
}
// TODO(crbug.com/806868): Log in as a specific account, instead of always the
// primary.
identity::IdentityManager* identity_manager =
profile ? IdentityManagerFactory::GetForProfile(profile) : nullptr;
if (!identity_manager || !identity_manager->HasPrimaryAccount()) {
DLOG(ERROR) << "No primary account. Cannot authenticate.";
return nullptr;
}
return identity_manager;
}
autofill::PersonalDataManager* UiControllerAndroid::GetPersonalDataManager() {
return autofill::PersonalDataManagerFactory::GetForProfile(
ProfileManager::GetLastUsedProfile());
......
......@@ -47,6 +47,7 @@ class UiControllerAndroid : public UiController, public Client {
// Overrides Client:
std::string GetApiKey() override;
identity::IdentityManager* GetIdentityManagerForPrimaryAccount() override;
autofill::PersonalDataManager* GetPersonalDataManager() override;
std::string GetServerUrl() override;
UiController* GetUiController() override;
......
......@@ -88,6 +88,7 @@ jumbo_static_library("browser") {
"//content/public/browser",
"//google_apis",
"//net",
"//services/identity/public/cpp:cpp",
"//third_party/re2",
]
}
......
......@@ -8,6 +8,7 @@ include_rules = [
"+google_apis",
"+net",
"+services/network/public/cpp",
"+services/identity/public/cpp",
"+third_party/blink/public/mojom/payments/payment_request.mojom.h",
"+third_party/re2",
"+ui/base/l10n/l10n_util.h",
......
......@@ -7,6 +7,10 @@
#include <string>
namespace identity {
class IdentityManager;
} // namespace identity
namespace autofill {
class PersonalDataManager;
} // namespace autofill
......@@ -23,6 +27,10 @@ class Client {
// Returns the API key to be used for requests to the backend.
virtual std::string GetApiKey() = 0;
// Gets the identity manager to use to make authenticated calls or nullptr if
// no identity is available.
virtual identity::IdentityManager* GetIdentityManagerForPrimaryAccount() = 0;
// Returns the current active personal data manager.
virtual autofill::PersonalDataManager* GetPersonalDataManager() = 0;
......
......@@ -36,14 +36,14 @@ void Controller::CreateAndStartForWebContents(
std::unique_ptr<Client> client,
std::unique_ptr<std::map<std::string, std::string>> parameters) {
// Get the key early since |client| will be invalidated when moved below.
const std::string api_key = client->GetApiKey();
GURL server_url(client->GetServerUrl());
DCHECK(server_url.is_valid());
std::unique_ptr<Service> service = std::make_unique<Service>(
client->GetApiKey(), server_url, web_contents->GetBrowserContext(),
client->GetIdentityManagerForPrimaryAccount());
new Controller(web_contents, std::move(client),
WebController::CreateForWebContents(web_contents),
std::make_unique<Service>(api_key, server_url,
web_contents->GetBrowserContext()),
std::move(parameters));
std::move(service), std::move(parameters));
}
Service* Controller::GetService() {
......
......@@ -41,6 +41,9 @@ class FakeClient : public Client {
// Implements Client
std::string GetApiKey() override { return ""; }
identity::IdentityManager* GetIdentityManagerForPrimaryAccount() override {
return nullptr;
}
autofill::PersonalDataManager* GetPersonalDataManager() override {
return nullptr;
}
......
......@@ -8,7 +8,8 @@
namespace autofill_assistant {
MockService::MockService() : Service("api_key", GURL("http://fake"), nullptr) {}
MockService::MockService()
: Service("api_key", GURL("http://fake"), nullptr, nullptr) {}
MockService::~MockService() {}
} // namespace autofill_assistant
......@@ -8,19 +8,31 @@
#include <utility>
#include <vector>
#include "base/command_line.h"
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "components/autofill_assistant/browser/protocol_utils.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/storage_partition.h"
#include "net/base/load_flags.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_status_code.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "url/url_canon_stdstring.h"
namespace {
namespace switches {
// --autofill_assistant-auth=false disables authentication. This is only useful
// during development, as prod instances require authentication.
const char* const kAutofillAssistantAuth = "autofill-assistant-auth";
} // namespace switches
// TODO(crbug.com/806868): Provide correct server and endpoint.
const char* const kScriptEndpoint = "/v1/supportsSite2";
const char* const kActionEndpoint = "/v1/actions2";
const char* const kTokenFetchId = "autofill_assistant";
const char* const kOAuthScope =
"https://www.googleapis.com/auth/userinfo.profile";
net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation("autofill_service", R"(
......@@ -46,19 +58,25 @@ namespace autofill_assistant {
Service::Service(const std::string& api_key,
const GURL& server_url,
content::BrowserContext* context)
: context_(context) {
content::BrowserContext* context,
identity::IdentityManager* identity_manager)
: context_(context),
api_key_(api_key),
identity_manager_(identity_manager),
auth_enabled_(
identity_manager_ &&
"false" !=
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kAutofillAssistantAuth)),
weak_ptr_factory_(this) {
DCHECK(server_url.is_valid());
std::string api_key_query = base::StrCat({"key=", api_key});
url::StringPieceReplacements<std::string> script_replacements;
script_replacements.SetPathStr(kScriptEndpoint);
script_replacements.SetQueryStr(api_key_query);
script_server_url_ = server_url.ReplaceComponents(script_replacements);
url::StringPieceReplacements<std::string> action_replacements;
action_replacements.SetPathStr(kActionEndpoint);
action_replacements.SetQueryStr(api_key_query);
script_action_server_url_ = server_url.ReplaceComponents(action_replacements);
}
......@@ -70,12 +88,9 @@ void Service::GetScriptsForUrl(
ResponseCallback callback) {
DCHECK(url.is_valid());
std::unique_ptr<Loader> loader = std::make_unique<Loader>();
loader->callback = std::move(callback);
loader->loader = CreateAndStartLoader(
script_server_url_,
ProtocolUtils::CreateGetScriptsRequest(url, parameters), loader.get());
loaders_[loader.get()] = std::move(loader);
SendRequest(AddLoader(script_server_url_,
ProtocolUtils::CreateGetScriptsRequest(url, parameters),
std::move(callback)));
}
void Service::GetActions(const std::string& script_path,
......@@ -84,14 +99,10 @@ void Service::GetActions(const std::string& script_path,
ResponseCallback callback) {
DCHECK(!script_path.empty());
std::unique_ptr<Loader> loader = std::make_unique<Loader>();
loader->callback = std::move(callback);
loader->loader =
CreateAndStartLoader(script_action_server_url_,
ProtocolUtils::CreateInitialScriptActionsRequest(
script_path, url, parameters),
loader.get());
loaders_[loader.get()] = std::move(loader);
SendRequest(AddLoader(script_action_server_url_,
ProtocolUtils::CreateInitialScriptActionsRequest(
script_path, url, parameters),
std::move(callback)));
}
void Service::GetNextActions(
......@@ -100,60 +111,103 @@ void Service::GetNextActions(
ResponseCallback callback) {
DCHECK(!previous_server_payload.empty());
SendRequest(AddLoader(script_action_server_url_,
ProtocolUtils::CreateNextScriptActionsRequest(
previous_server_payload, processed_actions),
std::move(callback)));
}
void Service::SendRequest(Loader* loader) {
if (access_token_.empty() && auth_enabled_) {
// Trigger a fetch of the access token. All loaders in loaders_ will be
// started later on, the access token is available.
FetchAccessToken();
return;
}
StartLoader(loader);
}
Service::Loader::Loader() : retried_with_fresh_access_token(false) {}
Service::Loader::~Loader() {}
Service::Loader* Service::AddLoader(const GURL& url,
const std::string& request_body,
ResponseCallback callback) {
std::unique_ptr<Loader> loader = std::make_unique<Loader>();
loader->url = url;
loader->request_body = request_body;
loader->callback = std::move(callback);
loader->loader =
CreateAndStartLoader(script_action_server_url_,
ProtocolUtils::CreateNextScriptActionsRequest(
previous_server_payload, processed_actions),
loader.get());
loaders_[loader.get()] = std::move(loader);
Loader* loader_ptr = loader.get();
loaders_[loader_ptr] = std::move(loader);
return loader_ptr;
}
Service::Loader::Loader() {}
Service::Loader::~Loader() {}
void Service::StartLoader(Loader* loader) {
if (loader->loader)
return;
std::unique_ptr<::network::SimpleURLLoader> Service::CreateAndStartLoader(
const GURL& server_url,
const std::string& request,
Loader* loader) {
auto resource_request = std::make_unique<::network::ResourceRequest>();
resource_request->url = server_url;
resource_request->method = "POST";
resource_request->fetch_redirect_mode =
::network::mojom::FetchRedirectMode::kError;
resource_request->load_flags =
net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SAVE_COOKIES;
std::unique_ptr<::network::SimpleURLLoader> simple_loader =
::network::SimpleURLLoader::Create(std::move(resource_request),
traffic_annotation);
simple_loader->AttachStringForUpload(request, "application/x-protobuffer");
if (access_token_.empty()) {
url::StringPieceReplacements<std::string> add_key;
add_key.SetQueryStr(base::StrCat({"key=", api_key_}));
resource_request->url = loader->url.ReplaceComponents(add_key);
} else {
resource_request->url = loader->url;
resource_request->headers.SetHeader(
"Authorization", base::StrCat({"Bearer ", access_token_}));
}
loader->loader = ::network::SimpleURLLoader::Create(
std::move(resource_request), traffic_annotation);
loader->loader->AttachStringForUpload(loader->request_body,
"application/x-protobuffer");
#ifdef DEBUG
simple_loader->SetAllowHttpErrorResults(true);
loader->loader->SetAllowHttpErrorResults(true);
#endif
simple_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
loader->loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
content::BrowserContext::GetDefaultStoragePartition(context_)
->GetURLLoaderFactoryForBrowserProcess()
.get(),
base::BindOnce(&Service::OnURLLoaderComplete, base::Unretained(this),
loader));
return simple_loader;
}
void Service::OnURLLoaderComplete(Loader* loader,
std::unique_ptr<std::string> response_body) {
auto loader_it = loaders_.find(loader);
DCHECK(loader_it != loaders_.end());
int response_code = 0;
if (loader->loader->ResponseInfo() &&
loader->loader->ResponseInfo()->headers) {
response_code = loader->loader->ResponseInfo()->headers->response_code();
}
// When getting a 401, refresh the auth token - but only try this once.
if (response_code == 401 && auth_enabled_ && !access_token_.empty() &&
!loader->retried_with_fresh_access_token) {
loader->retried_with_fresh_access_token = true;
loader->loader.reset();
// Invalidate access token and load a new one.
OAuth2TokenService::ScopeSet scopes{kOAuthScope};
identity_manager_->RemoveAccessTokenFromCache(account_id_, scopes,
access_token_);
access_token_.clear();
SendRequest(loader);
return;
}
// Take ownership of loader.
std::unique_ptr<Loader> loader_instance = std::move(loader_it->second);
loaders_.erase(loader_it);
DCHECK(loader_instance);
int response_code = 0;
if (loader_instance->loader->ResponseInfo() &&
loader_instance->loader->ResponseInfo()->headers) {
response_code =
loader_instance->loader->ResponseInfo()->headers->response_code();
}
std::string response_body_str;
if (loader_instance->loader->NetError() != net::OK || response_code != 200) {
LOG(ERROR) << "Communicating with autofill assistant server error NetError="
......@@ -169,4 +223,37 @@ void Service::OnURLLoaderComplete(Loader* loader,
std::move(loader_instance->callback).Run(true, response_body_str);
}
void Service::FetchAccessToken() {
DCHECK(identity_manager_);
if (token_fetcher_)
return;
OAuth2TokenService::ScopeSet scopes{kOAuthScope};
account_id_ = identity_manager_->GetPrimaryAccountId();
token_fetcher_ = std::make_unique<identity::PrimaryAccountAccessTokenFetcher>(
kTokenFetchId, identity_manager_, scopes,
base::BindOnce(&Service::OnFetchAccessToken, base::Unretained(this)),
identity::PrimaryAccountAccessTokenFetcher::Mode::kWaitUntilAvailable);
// Unretained, because token_fetcher_ is owned by this instance.
}
void Service::OnFetchAccessToken(GoogleServiceAuthError error,
identity::AccessTokenInfo access_token_info) {
token_fetcher_.reset();
if (error.state() != GoogleServiceAuthError::NONE) {
auth_enabled_ = false;
// Give up on authentication for this run. Let the pending requests through,
// which might be rejected, depending on the server configuration.
return;
}
access_token_ = access_token_info.token;
// Start any pending requests with the access token.
for (const auto& entry : loaders_) {
StartLoader(entry.first);
}
}
} // namespace autofill_assistant
......@@ -11,22 +11,32 @@
#include <vector>
#include "base/callback.h"
#include "base/memory/weak_ptr.h"
#include "components/autofill_assistant/browser/service.pb.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "services/identity/public/cpp/primary_account_access_token_fetcher.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "url/gurl.h"
namespace content {
class BrowserContext;
}
} // namespace content
namespace identity {
class IdentityManager;
} // namespace identity
namespace autofill_assistant {
// Autofill assistant service to communicate with the server to get scripts and
// client actions.
class Service {
public:
// |context| and |identity_manager| must remain valid for the lifetime of the
// service instance. |identity_manager| might be nullptr.
Service(const std::string& api_key,
const GURL& server_url,
content::BrowserContext* context);
content::BrowserContext* context,
identity::IdentityManager* identity_manager);
virtual ~Service();
using ResponseCallback =
......@@ -56,16 +66,32 @@ class Service {
Loader();
~Loader();
GURL url;
std::string request_body;
ResponseCallback callback;
std::unique_ptr<::network::SimpleURLLoader> loader;
bool retried_with_fresh_access_token;
};
std::unique_ptr<::network::SimpleURLLoader> CreateAndStartLoader(
const GURL& server_url,
const std::string& request,
Loader* loader);
void SendRequest(Loader* loader);
// Creates a loader and adds it to |loaders_|.
Loader* AddLoader(const GURL& url,
const std::string& request_body,
ResponseCallback callback);
// Sends a request with the given loader, using the current auth token, if one
// is available.
void StartLoader(Loader* loader);
void OnURLLoaderComplete(Loader* loader,
std::unique_ptr<std::string> response_body);
// Fetches the access token and, once this is done, starts all pending loaders
// in |loaders_|.
void FetchAccessToken();
void OnFetchAccessToken(GoogleServiceAuthError error,
identity::AccessTokenInfo access_token_info);
content::BrowserContext* context_;
GURL script_server_url_;
GURL script_action_server_url_;
......@@ -73,6 +99,28 @@ class Service {
// Destroying this object will cancel ongoing requests.
std::map<Loader*, std::unique_ptr<Loader>> loaders_;
// API key to add to the URL of unauthenticated requests.
std::string api_key_;
// Pointer must remain valid for the lifetime of the Service instance. Might
// be nullptr, if auth_enabled_ is false.
identity::IdentityManager* const identity_manager_;
// Whether requests should be authenticated.
bool auth_enabled_;
// An OAuth 2 token. Empty if not fetched yet or if the token has been
// invalidated.
std::string access_token_;
// Account id |access_token_| is for.
std::string account_id_;
// Active OAuth2 token fetcher.
std::unique_ptr<identity::PrimaryAccountAccessTokenFetcher> token_fetcher_;
base::WeakPtrFactory<Service> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(Service);
};
......
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