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 @@ ...@@ -14,7 +14,9 @@
#include "base/command_line.h" #include "base/command_line.h"
#include "chrome/browser/android/chrome_feature_list.h" #include "chrome/browser/android/chrome_feature_list.h"
#include "chrome/browser/autofill/personal_data_manager_factory.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/profiles/profile_manager.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/common/channel_info.h" #include "chrome/common/channel_info.h"
#include "components/autofill_assistant/browser/controller.h" #include "components/autofill_assistant/browser/controller.h"
#include "components/variations/variations_associated_data.h" #include "components/variations/variations_associated_data.h"
...@@ -279,6 +281,24 @@ std::string UiControllerAndroid::GetApiKey() { ...@@ -279,6 +281,24 @@ std::string UiControllerAndroid::GetApiKey() {
return api_key; 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() { autofill::PersonalDataManager* UiControllerAndroid::GetPersonalDataManager() {
return autofill::PersonalDataManagerFactory::GetForProfile( return autofill::PersonalDataManagerFactory::GetForProfile(
ProfileManager::GetLastUsedProfile()); ProfileManager::GetLastUsedProfile());
......
...@@ -47,6 +47,7 @@ class UiControllerAndroid : public UiController, public Client { ...@@ -47,6 +47,7 @@ class UiControllerAndroid : public UiController, public Client {
// Overrides Client: // Overrides Client:
std::string GetApiKey() override; std::string GetApiKey() override;
identity::IdentityManager* GetIdentityManagerForPrimaryAccount() override;
autofill::PersonalDataManager* GetPersonalDataManager() override; autofill::PersonalDataManager* GetPersonalDataManager() override;
std::string GetServerUrl() override; std::string GetServerUrl() override;
UiController* GetUiController() override; UiController* GetUiController() override;
......
...@@ -88,6 +88,7 @@ jumbo_static_library("browser") { ...@@ -88,6 +88,7 @@ jumbo_static_library("browser") {
"//content/public/browser", "//content/public/browser",
"//google_apis", "//google_apis",
"//net", "//net",
"//services/identity/public/cpp:cpp",
"//third_party/re2", "//third_party/re2",
] ]
} }
......
...@@ -8,6 +8,7 @@ include_rules = [ ...@@ -8,6 +8,7 @@ include_rules = [
"+google_apis", "+google_apis",
"+net", "+net",
"+services/network/public/cpp", "+services/network/public/cpp",
"+services/identity/public/cpp",
"+third_party/blink/public/mojom/payments/payment_request.mojom.h", "+third_party/blink/public/mojom/payments/payment_request.mojom.h",
"+third_party/re2", "+third_party/re2",
"+ui/base/l10n/l10n_util.h", "+ui/base/l10n/l10n_util.h",
......
...@@ -7,6 +7,10 @@ ...@@ -7,6 +7,10 @@
#include <string> #include <string>
namespace identity {
class IdentityManager;
} // namespace identity
namespace autofill { namespace autofill {
class PersonalDataManager; class PersonalDataManager;
} // namespace autofill } // namespace autofill
...@@ -23,6 +27,10 @@ class Client { ...@@ -23,6 +27,10 @@ class Client {
// Returns the API key to be used for requests to the backend. // Returns the API key to be used for requests to the backend.
virtual std::string GetApiKey() = 0; 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. // Returns the current active personal data manager.
virtual autofill::PersonalDataManager* GetPersonalDataManager() = 0; virtual autofill::PersonalDataManager* GetPersonalDataManager() = 0;
......
...@@ -36,14 +36,14 @@ void Controller::CreateAndStartForWebContents( ...@@ -36,14 +36,14 @@ void Controller::CreateAndStartForWebContents(
std::unique_ptr<Client> client, std::unique_ptr<Client> client,
std::unique_ptr<std::map<std::string, std::string>> parameters) { std::unique_ptr<std::map<std::string, std::string>> parameters) {
// Get the key early since |client| will be invalidated when moved below. // Get the key early since |client| will be invalidated when moved below.
const std::string api_key = client->GetApiKey();
GURL server_url(client->GetServerUrl()); GURL server_url(client->GetServerUrl());
DCHECK(server_url.is_valid()); 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), new Controller(web_contents, std::move(client),
WebController::CreateForWebContents(web_contents), WebController::CreateForWebContents(web_contents),
std::make_unique<Service>(api_key, server_url, std::move(service), std::move(parameters));
web_contents->GetBrowserContext()),
std::move(parameters));
} }
Service* Controller::GetService() { Service* Controller::GetService() {
......
...@@ -41,6 +41,9 @@ class FakeClient : public Client { ...@@ -41,6 +41,9 @@ class FakeClient : public Client {
// Implements Client // Implements Client
std::string GetApiKey() override { return ""; } std::string GetApiKey() override { return ""; }
identity::IdentityManager* GetIdentityManagerForPrimaryAccount() override {
return nullptr;
}
autofill::PersonalDataManager* GetPersonalDataManager() override { autofill::PersonalDataManager* GetPersonalDataManager() override {
return nullptr; return nullptr;
} }
......
...@@ -8,7 +8,8 @@ ...@@ -8,7 +8,8 @@
namespace autofill_assistant { namespace autofill_assistant {
MockService::MockService() : Service("api_key", GURL("http://fake"), nullptr) {} MockService::MockService()
: Service("api_key", GURL("http://fake"), nullptr, nullptr) {}
MockService::~MockService() {} MockService::~MockService() {}
} // namespace autofill_assistant } // namespace autofill_assistant
...@@ -8,19 +8,31 @@ ...@@ -8,19 +8,31 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "base/command_line.h"
#include "base/strings/strcat.h" #include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "components/autofill_assistant/browser/protocol_utils.h" #include "components/autofill_assistant/browser/protocol_utils.h"
#include "content/public/browser/browser_context.h" #include "content/public/browser/browser_context.h"
#include "content/public/browser/storage_partition.h" #include "content/public/browser/storage_partition.h"
#include "net/base/load_flags.h" #include "net/base/load_flags.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_status_code.h" #include "net/http/http_status_code.h"
#include "net/traffic_annotation/network_traffic_annotation.h" #include "net/traffic_annotation/network_traffic_annotation.h"
#include "url/url_canon_stdstring.h" #include "url/url_canon_stdstring.h"
namespace { 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. // TODO(crbug.com/806868): Provide correct server and endpoint.
const char* const kScriptEndpoint = "/v1/supportsSite2"; const char* const kScriptEndpoint = "/v1/supportsSite2";
const char* const kActionEndpoint = "/v1/actions2"; 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::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation("autofill_service", R"( net::DefineNetworkTrafficAnnotation("autofill_service", R"(
...@@ -46,19 +58,25 @@ namespace autofill_assistant { ...@@ -46,19 +58,25 @@ namespace autofill_assistant {
Service::Service(const std::string& api_key, Service::Service(const std::string& api_key,
const GURL& server_url, const GURL& server_url,
content::BrowserContext* context) content::BrowserContext* context,
: context_(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()); DCHECK(server_url.is_valid());
std::string api_key_query = base::StrCat({"key=", api_key});
url::StringPieceReplacements<std::string> script_replacements; url::StringPieceReplacements<std::string> script_replacements;
script_replacements.SetPathStr(kScriptEndpoint); script_replacements.SetPathStr(kScriptEndpoint);
script_replacements.SetQueryStr(api_key_query);
script_server_url_ = server_url.ReplaceComponents(script_replacements); script_server_url_ = server_url.ReplaceComponents(script_replacements);
url::StringPieceReplacements<std::string> action_replacements; url::StringPieceReplacements<std::string> action_replacements;
action_replacements.SetPathStr(kActionEndpoint); action_replacements.SetPathStr(kActionEndpoint);
action_replacements.SetQueryStr(api_key_query);
script_action_server_url_ = server_url.ReplaceComponents(action_replacements); script_action_server_url_ = server_url.ReplaceComponents(action_replacements);
} }
...@@ -70,12 +88,9 @@ void Service::GetScriptsForUrl( ...@@ -70,12 +88,9 @@ void Service::GetScriptsForUrl(
ResponseCallback callback) { ResponseCallback callback) {
DCHECK(url.is_valid()); DCHECK(url.is_valid());
std::unique_ptr<Loader> loader = std::make_unique<Loader>(); SendRequest(AddLoader(script_server_url_,
loader->callback = std::move(callback); ProtocolUtils::CreateGetScriptsRequest(url, parameters),
loader->loader = CreateAndStartLoader( std::move(callback)));
script_server_url_,
ProtocolUtils::CreateGetScriptsRequest(url, parameters), loader.get());
loaders_[loader.get()] = std::move(loader);
} }
void Service::GetActions(const std::string& script_path, void Service::GetActions(const std::string& script_path,
...@@ -84,14 +99,10 @@ void Service::GetActions(const std::string& script_path, ...@@ -84,14 +99,10 @@ void Service::GetActions(const std::string& script_path,
ResponseCallback callback) { ResponseCallback callback) {
DCHECK(!script_path.empty()); DCHECK(!script_path.empty());
std::unique_ptr<Loader> loader = std::make_unique<Loader>(); SendRequest(AddLoader(script_action_server_url_,
loader->callback = std::move(callback); ProtocolUtils::CreateInitialScriptActionsRequest(
loader->loader = script_path, url, parameters),
CreateAndStartLoader(script_action_server_url_, std::move(callback)));
ProtocolUtils::CreateInitialScriptActionsRequest(
script_path, url, parameters),
loader.get());
loaders_[loader.get()] = std::move(loader);
} }
void Service::GetNextActions( void Service::GetNextActions(
...@@ -100,60 +111,103 @@ void Service::GetNextActions( ...@@ -100,60 +111,103 @@ void Service::GetNextActions(
ResponseCallback callback) { ResponseCallback callback) {
DCHECK(!previous_server_payload.empty()); 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>(); std::unique_ptr<Loader> loader = std::make_unique<Loader>();
loader->url = url;
loader->request_body = request_body;
loader->callback = std::move(callback); loader->callback = std::move(callback);
loader->loader = Loader* loader_ptr = loader.get();
CreateAndStartLoader(script_action_server_url_, loaders_[loader_ptr] = std::move(loader);
ProtocolUtils::CreateNextScriptActionsRequest( return loader_ptr;
previous_server_payload, processed_actions),
loader.get());
loaders_[loader.get()] = std::move(loader);
} }
Service::Loader::Loader() {} void Service::StartLoader(Loader* loader) {
Service::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>(); auto resource_request = std::make_unique<::network::ResourceRequest>();
resource_request->url = server_url;
resource_request->method = "POST"; resource_request->method = "POST";
resource_request->fetch_redirect_mode = resource_request->fetch_redirect_mode =
::network::mojom::FetchRedirectMode::kError; ::network::mojom::FetchRedirectMode::kError;
resource_request->load_flags = resource_request->load_flags =
net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SAVE_COOKIES; net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SAVE_COOKIES;
std::unique_ptr<::network::SimpleURLLoader> simple_loader = if (access_token_.empty()) {
::network::SimpleURLLoader::Create(std::move(resource_request), url::StringPieceReplacements<std::string> add_key;
traffic_annotation); add_key.SetQueryStr(base::StrCat({"key=", api_key_}));
simple_loader->AttachStringForUpload(request, "application/x-protobuffer"); 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 #ifdef DEBUG
simple_loader->SetAllowHttpErrorResults(true); loader->loader->SetAllowHttpErrorResults(true);
#endif #endif
simple_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie( loader->loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
content::BrowserContext::GetDefaultStoragePartition(context_) content::BrowserContext::GetDefaultStoragePartition(context_)
->GetURLLoaderFactoryForBrowserProcess() ->GetURLLoaderFactoryForBrowserProcess()
.get(), .get(),
base::BindOnce(&Service::OnURLLoaderComplete, base::Unretained(this), base::BindOnce(&Service::OnURLLoaderComplete, base::Unretained(this),
loader)); loader));
return simple_loader;
} }
void Service::OnURLLoaderComplete(Loader* loader, void Service::OnURLLoaderComplete(Loader* loader,
std::unique_ptr<std::string> response_body) { std::unique_ptr<std::string> response_body) {
auto loader_it = loaders_.find(loader); auto loader_it = loaders_.find(loader);
DCHECK(loader_it != loaders_.end()); 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); std::unique_ptr<Loader> loader_instance = std::move(loader_it->second);
loaders_.erase(loader_it); loaders_.erase(loader_it);
DCHECK(loader_instance); 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; std::string response_body_str;
if (loader_instance->loader->NetError() != net::OK || response_code != 200) { if (loader_instance->loader->NetError() != net::OK || response_code != 200) {
LOG(ERROR) << "Communicating with autofill assistant server error NetError=" LOG(ERROR) << "Communicating with autofill assistant server error NetError="
...@@ -169,4 +223,37 @@ void Service::OnURLLoaderComplete(Loader* loader, ...@@ -169,4 +223,37 @@ void Service::OnURLLoaderComplete(Loader* loader,
std::move(loader_instance->callback).Run(true, response_body_str); 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 } // namespace autofill_assistant
...@@ -11,22 +11,32 @@ ...@@ -11,22 +11,32 @@
#include <vector> #include <vector>
#include "base/callback.h" #include "base/callback.h"
#include "base/memory/weak_ptr.h"
#include "components/autofill_assistant/browser/service.pb.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 "services/network/public/cpp/simple_url_loader.h"
#include "url/gurl.h" #include "url/gurl.h"
namespace content { namespace content {
class BrowserContext; class BrowserContext;
} } // namespace content
namespace identity {
class IdentityManager;
} // namespace identity
namespace autofill_assistant { namespace autofill_assistant {
// Autofill assistant service to communicate with the server to get scripts and // Autofill assistant service to communicate with the server to get scripts and
// client actions. // client actions.
class Service { class Service {
public: 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, Service(const std::string& api_key,
const GURL& server_url, const GURL& server_url,
content::BrowserContext* context); content::BrowserContext* context,
identity::IdentityManager* identity_manager);
virtual ~Service(); virtual ~Service();
using ResponseCallback = using ResponseCallback =
...@@ -56,16 +66,32 @@ class Service { ...@@ -56,16 +66,32 @@ class Service {
Loader(); Loader();
~Loader(); ~Loader();
GURL url;
std::string request_body;
ResponseCallback callback; ResponseCallback callback;
std::unique_ptr<::network::SimpleURLLoader> loader; std::unique_ptr<::network::SimpleURLLoader> loader;
bool retried_with_fresh_access_token;
}; };
std::unique_ptr<::network::SimpleURLLoader> CreateAndStartLoader(
const GURL& server_url, void SendRequest(Loader* loader);
const std::string& request,
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, void OnURLLoaderComplete(Loader* loader,
std::unique_ptr<std::string> response_body); 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_; content::BrowserContext* context_;
GURL script_server_url_; GURL script_server_url_;
GURL script_action_server_url_; GURL script_action_server_url_;
...@@ -73,6 +99,28 @@ class Service { ...@@ -73,6 +99,28 @@ class Service {
// Destroying this object will cancel ongoing requests. // Destroying this object will cancel ongoing requests.
std::map<Loader*, std::unique_ptr<Loader>> loaders_; 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); 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