// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "google_apis/gaia/gaia_oauth_client.h" #include #include #include "base/bind.h" #include "base/check.h" #include "base/check_op.h" #include "base/json/json_reader.h" #include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" #include "base/notreached.h" #include "base/strings/escape.h" #include "base/strings/strcat.h" #include "base/strings/string_util.h" #include "base/threading/thread_task_runner_handle.h" #include "base/values.h" #include "google_apis/gaia/gaia_auth_util.h" #include "google_apis/gaia/gaia_urls.h" #include "net/base/backoff_entry.h" #include "net/base/load_flags.h" #include "net/http/http_status_code.h" #include "net/traffic_annotation/network_traffic_annotation.h" #include "net/url_request/url_request_throttler_entry.h" #include "services/network/public/cpp/resource_request.h" #include "services/network/public/cpp/shared_url_loader_factory.h" #include "services/network/public/cpp/simple_url_loader.h" #include "services/network/public/mojom/url_response_head.mojom.h" #include "url/gurl.h" namespace { const char kAccessTokenValue[] = "access_token"; const char kRefreshTokenValue[] = "refresh_token"; const char kExpiresInValue[] = "expires_in"; } namespace gaia { class GaiaOAuthClient::Core : public base::RefCountedThreadSafe { public: Core(scoped_refptr url_loader_factory) : backoff_entry_(&backoff_policy_), num_retries_(0), max_retries_(0), url_loader_factory_(url_loader_factory), delegate_(nullptr), request_type_(NO_PENDING_REQUEST) { backoff_policy_.num_errors_to_ignore = net::URLRequestThrottlerEntry::kDefaultNumErrorsToIgnore; backoff_policy_.initial_delay_ms = net::URLRequestThrottlerEntry::kDefaultInitialDelayMs; backoff_policy_.multiply_factor = net::URLRequestThrottlerEntry::kDefaultMultiplyFactor; backoff_policy_.jitter_factor = net::URLRequestThrottlerEntry::kDefaultJitterFactor; backoff_policy_.maximum_backoff_ms = net::URLRequestThrottlerEntry::kDefaultMaximumBackoffMs; backoff_policy_.entry_lifetime_ms = net::URLRequestThrottlerEntry::kDefaultEntryLifetimeMs; backoff_policy_.always_use_initial_delay = false; } void GetTokensFromAuthCode(const OAuthClientInfo& oauth_client_info, const std::string& auth_code, int max_retries, GaiaOAuthClient::Delegate* delegate); void RefreshToken(const OAuthClientInfo& oauth_client_info, const std::string& refresh_token, const std::vector& scopes, int max_retries, GaiaOAuthClient::Delegate* delegate); void GetUserEmail(const std::string& oauth_access_token, int max_retries, Delegate* delegate); void GetUserId(const std::string& oauth_access_token, int max_retries, Delegate* delegate); void GetUserInfo(const std::string& oauth_access_token, int max_retries, Delegate* delegate); void GetAccountCapabilities( const std::string& oauth_access_token, const std::vector& capabilities_names, int max_retries, Delegate* delegate); void GetTokenInfo(const std::string& qualifier, const std::string& query, int max_retries, Delegate* delegate); // Called as a SimpleURLLoader callback void OnURLLoadComplete(std::unique_ptr body); private: friend class base::RefCountedThreadSafe; enum RequestType { NO_PENDING_REQUEST, TOKENS_FROM_AUTH_CODE, REFRESH_TOKEN, TOKEN_INFO, USER_EMAIL, USER_ID, USER_INFO, ACCOUNT_CAPABILITIES, }; ~Core() {} void GetUserInfoImpl(RequestType type, const std::string& oauth_access_token, int max_retries, Delegate* delegate); // Stores request settings into |this| and calls SendRequest(). void MakeRequest( RequestType type, const GURL& url, std::string post_body /* may be empty if not needed*/, std::string authorization_header /* empty if not needed */, std::string http_method_override_header /* empty if not needed */, int max_retries, GaiaOAuthClient::Delegate* delegate, const net::MutableNetworkTrafficAnnotationTag& traffic_annotation); // Sends out a request based on things MakeRequest() stored... // once |backoff_entry_| says it's OK. Can be called many times to retry, // assuming |request_| is destroyed first. void SendRequest(); // Actually sends the request. void SendRequestImpl(); void HandleResponse(std::unique_ptr body, bool* should_retry_request); net::BackoffEntry::Policy backoff_policy_; net::BackoffEntry backoff_entry_; int num_retries_; int max_retries_; GURL url_; net::MutableNetworkTrafficAnnotationTag traffic_annotation_; std::string post_body_; std::string authorization_header_; std::string http_method_override_header_; scoped_refptr url_loader_factory_; raw_ptr delegate_; std::unique_ptr request_; RequestType request_type_; base::WeakPtrFactory weak_ptr_factory_{this}; }; void GaiaOAuthClient::Core::GetTokensFromAuthCode( const OAuthClientInfo& oauth_client_info, const std::string& auth_code, int max_retries, GaiaOAuthClient::Delegate* delegate) { std::string post_body = "code=" + base::EscapeUrlEncodedData(auth_code, true) + "&client_id=" + base::EscapeUrlEncodedData(oauth_client_info.client_id, true) + "&client_secret=" + base::EscapeUrlEncodedData(oauth_client_info.client_secret, true) + "&redirect_uri=" + base::EscapeUrlEncodedData(oauth_client_info.redirect_uri, true) + "&grant_type=authorization_code"; net::MutableNetworkTrafficAnnotationTag traffic_annotation( net::DefineNetworkTrafficAnnotation("gaia_oauth_client_get_tokens", R"( semantics { sender: "OAuth 2.0 calls" description: "This request exchanges an authorization code for an OAuth 2.0 " "refresh token and an OAuth 2.0 access token." trigger: "This request is triggered when a Chrome service requires an " "access token and a refresh token (e.g. Cloud Print, Chrome Remote " "Desktop etc.) See https://developers.google.com/identity/protocols" "/OAuth2 for more information about the Google implementation of " "the OAuth 2.0 protocol." data: "The Google console client ID and client secret of the caller, the " "OAuth authorization code and the redirect URI." 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 { policy_options {mode: MANDATORY} SigninAllowed: false } } })")); MakeRequest(TOKENS_FROM_AUTH_CODE, GURL(GaiaUrls::GetInstance()->oauth2_token_url()), post_body, /* authorization_header = */ std::string(), /* http_method_override_header = */ std::string(), max_retries, delegate, traffic_annotation); } void GaiaOAuthClient::Core::RefreshToken( const OAuthClientInfo& oauth_client_info, const std::string& refresh_token, const std::vector& scopes, int max_retries, GaiaOAuthClient::Delegate* delegate) { std::string post_body = "refresh_token=" + base::EscapeUrlEncodedData(refresh_token, true) + "&client_id=" + base::EscapeUrlEncodedData(oauth_client_info.client_id, true) + "&client_secret=" + base::EscapeUrlEncodedData(oauth_client_info.client_secret, true) + "&grant_type=refresh_token"; if (!scopes.empty()) { std::string scopes_string = base::JoinString(scopes, " "); post_body += "&scope=" + base::EscapeUrlEncodedData(scopes_string, true); } net::MutableNetworkTrafficAnnotationTag traffic_annotation( net::DefineNetworkTrafficAnnotation("gaia_oauth_client_refresh_token", R"( semantics { sender: "OAuth 2.0 calls" description: "This request fetches a fresh access token that can be used to " "authenticate an API call to a Google web endpoint." trigger: "This is called whenever the caller needs a fresh OAuth 2.0 access " "token." data: "The OAuth 2.0 refresh token, the Google console client ID and " "client secret of the caller, and optionally the scopes of the API " "for which the access token should be authorized." 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 { policy_options {mode: MANDATORY} SigninAllowed: false } } })")); MakeRequest(REFRESH_TOKEN, GURL(GaiaUrls::GetInstance()->oauth2_token_url()), post_body, /* authorization_header = */ std::string(), /* http_method_override_header = */ std::string(), max_retries, delegate, traffic_annotation); } void GaiaOAuthClient::Core::GetUserEmail(const std::string& oauth_access_token, int max_retries, Delegate* delegate) { GetUserInfoImpl(USER_EMAIL, oauth_access_token, max_retries, delegate); } void GaiaOAuthClient::Core::GetUserId(const std::string& oauth_access_token, int max_retries, Delegate* delegate) { GetUserInfoImpl(USER_ID, oauth_access_token, max_retries, delegate); } void GaiaOAuthClient::Core::GetUserInfo(const std::string& oauth_access_token, int max_retries, Delegate* delegate) { GetUserInfoImpl(USER_INFO, oauth_access_token, max_retries, delegate); } void GaiaOAuthClient::Core::GetUserInfoImpl( RequestType type, const std::string& oauth_access_token, int max_retries, Delegate* delegate) { net::MutableNetworkTrafficAnnotationTag traffic_annotation( net::DefineNetworkTrafficAnnotation("gaia_oauth_client_get_user_info", R"( semantics { sender: "OAuth 2.0 calls" description: "This request is used to fetch profile information about the user, " "like the email, the ID of the account, the full name, and the " "profile picture." trigger: "The main trigger for this request is in the AccountTrackerService " "that fetches the user info soon after the user signs in." data: "The OAuth 2.0 access token of the account." 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 { policy_options {mode: MANDATORY} SigninAllowed: false } } })")); std::string auth = "OAuth " + oauth_access_token; MakeRequest(type, GaiaUrls::GetInstance()->oauth_user_info_url(), /* post_body = */ std::string(), auth, /* http_method_override_header = */ std::string(), max_retries, delegate, traffic_annotation); } void GaiaOAuthClient::Core::GetTokenInfo(const std::string& qualifier, const std::string& query, int max_retries, Delegate* delegate) { std::string post_body = qualifier + "=" + base::EscapeUrlEncodedData(query, true); net::MutableNetworkTrafficAnnotationTag traffic_annotation( net::DefineNetworkTrafficAnnotation("gaia_oauth_client_get_token_info", R"( semantics { sender: "OAuth 2.0 calls" description: "This request fetches information about an OAuth 2.0 access token. " "The response is a dictionary of response values. The provided " "access token may have any scope, and basic results will be " "returned: issued_to, audience, scope, expires_in, access_type. In " "addition, if the https://www.googleapis.com/auth/userinfo.email " "scope is present, the email and verified_email fields will be " "returned. If the https://www.googleapis.com/auth/userinfo.profile " "scope is present, the user_id field will be returned." trigger: "This is triggered after a Google account is added to the browser. " "It it also triggered after each successful fetch of an OAuth 2.0 " "access token." data: "The OAuth 2.0 access token." 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 { policy_options {mode: MANDATORY} SigninAllowed: false } } })")); MakeRequest(TOKEN_INFO, GURL(GaiaUrls::GetInstance()->oauth2_token_info_url()), post_body, /* authorization_header = */ std::string(), /* http_method_override_header = */ std::string(), max_retries, delegate, traffic_annotation); } void GaiaOAuthClient::Core::GetAccountCapabilities( const std::string& oauth_access_token, const std::vector& capabilities_names, int max_retries, Delegate* delegate) { DCHECK(!capabilities_names.empty()); std::string post_body = base::StrCat({"names=", base::EscapeUrlEncodedData( *capabilities_names.begin(), true)}); for (auto it = capabilities_names.begin() + 1; it != capabilities_names.end(); ++it) { base::StrAppend(&post_body, {"&names=", base::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, /*http_method_override_header=*/"GET", max_retries, delegate, traffic_annotation); } void GaiaOAuthClient::Core::MakeRequest( RequestType type, const GURL& url, std::string post_body, std::string authorization_header, std::string http_method_override_header, int max_retries, GaiaOAuthClient::Delegate* delegate, const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) { DCHECK_EQ(request_type_, NO_PENDING_REQUEST); request_type_ = type; delegate_ = delegate; num_retries_ = 0; max_retries_ = max_retries; url_ = url; traffic_annotation_ = traffic_annotation; post_body_ = std::move(post_body); authorization_header_ = std::move(authorization_header); http_method_override_header_ = std::move(http_method_override_header); SendRequest(); } void GaiaOAuthClient::Core::SendRequest() { if (backoff_entry_.ShouldRejectRequest()) { base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( FROM_HERE, base::BindOnce(&GaiaOAuthClient::Core::SendRequestImpl, weak_ptr_factory_.GetWeakPtr()), backoff_entry_.GetTimeUntilRelease()); } else { SendRequestImpl(); } } void GaiaOAuthClient::Core::SendRequestImpl() { DCHECK(!request_.get()) << "Tried to fetch two things at once!"; auto resource_request = std::make_unique(); resource_request->url = url_; resource_request->method = post_body_.empty() ? "GET" : "POST"; resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit; if (!authorization_header_.empty()) resource_request->headers.SetHeader("Authorization", authorization_header_); if (!http_method_override_header_.empty()) { resource_request->headers.SetHeader("X-HTTP-Method-Override", http_method_override_header_); } request_ = network::SimpleURLLoader::Create( std::move(resource_request), static_cast(traffic_annotation_)); if (!post_body_.empty()) { request_->AttachStringForUpload(post_body_, "application/x-www-form-urlencoded"); } // Retry is implemented internally. request_->SetRetryOptions(0, network::SimpleURLLoader::RETRY_NEVER); request_->DownloadToStringOfUnboundedSizeUntilCrashAndDie( url_loader_factory_.get(), // Unretained(this) is safe since |this| owns |request_|, and its deletion // will cancel the callback. base::BindOnce(&GaiaOAuthClient::Core::OnURLLoadComplete, base::Unretained(this))); } void GaiaOAuthClient::Core::OnURLLoadComplete( std::unique_ptr body) { bool should_retry = false; base::WeakPtr weak_this = weak_ptr_factory_.GetWeakPtr(); // HandleResponse() may delete |this| if it assigns |should_retry| == false. HandleResponse(std::move(body), &should_retry); if (should_retry) { num_retries_++; backoff_entry_.InformOfRequest(false); SendRequest(); } else { if (weak_this) backoff_entry_.InformOfRequest(true); } } void GaiaOAuthClient::Core::HandleResponse(std::unique_ptr body, bool* should_retry_request) { *should_retry_request = false; // Move ownership of the request fetcher into a local scoped_ptr which // will be nuked when we're done handling the request. std::unique_ptr source = std::move(request_); int response_code = -1; if (source->ResponseInfo() && source->ResponseInfo()->headers) response_code = source->ResponseInfo()->headers->response_code(); // HTTP_BAD_REQUEST means the arguments are invalid. HTTP_UNAUTHORIZED means // the access or refresh token is invalid. No point retrying. We are // done here. if (response_code == net::HTTP_BAD_REQUEST || response_code == net::HTTP_UNAUTHORIZED) { delegate_->OnOAuthError(); return; } std::unique_ptr response_dict; if (response_code == net::HTTP_OK && body) { std::string data = std::move(*body); std::unique_ptr message_value = base::JSONReader::ReadDeprecated(data); if (message_value.get() && message_value->is_dict()) { response_dict.reset( static_cast(message_value.release())); } } if (!response_dict.get()) { // If we don't have an access token yet and the the error was not // RC_BAD_REQUEST, we may need to retry. if ((max_retries_ != -1) && (num_retries_ >= max_retries_)) { // Retry limit reached. Give up. request_type_ = NO_PENDING_REQUEST; delegate_->OnNetworkError(response_code); } else { *should_retry_request = true; } return; } RequestType type = request_type_; request_type_ = NO_PENDING_REQUEST; switch (type) { case USER_EMAIL: { std::string email; response_dict->GetString("email", &email); delegate_->OnGetUserEmailResponse(email); break; } case USER_ID: { std::string id; response_dict->GetString("id", &id); delegate_->OnGetUserIdResponse(id); break; } case USER_INFO: { delegate_->OnGetUserInfoResponse(std::move(response_dict)); break; } case TOKEN_INFO: { delegate_->OnGetTokenInfoResponse(std::move(response_dict)); break; } case TOKENS_FROM_AUTH_CODE: case REFRESH_TOKEN: { std::string access_token; std::string refresh_token; int expires_in_seconds = 0; response_dict->GetString(kAccessTokenValue, &access_token); response_dict->GetString(kRefreshTokenValue, &refresh_token); response_dict->GetInteger(kExpiresInValue, &expires_in_seconds); if (access_token.empty()) { delegate_->OnOAuthError(); return; } if (type == REFRESH_TOKEN) { delegate_->OnRefreshTokenResponse(access_token, expires_in_seconds); } else { delegate_->OnGetTokensResponse(refresh_token, access_token, expires_in_seconds); } break; } case ACCOUNT_CAPABILITIES: { delegate_->OnGetAccountCapabilitiesResponse(std::move(response_dict)); break; } case NO_PENDING_REQUEST: NOTREACHED(); } } GaiaOAuthClient::GaiaOAuthClient( scoped_refptr url_loader_factory) { core_ = new Core(std::move(url_loader_factory)); } GaiaOAuthClient::~GaiaOAuthClient() { } void GaiaOAuthClient::GetTokensFromAuthCode( const OAuthClientInfo& oauth_client_info, const std::string& auth_code, int max_retries, Delegate* delegate) { return core_->GetTokensFromAuthCode(oauth_client_info, auth_code, max_retries, delegate); } void GaiaOAuthClient::RefreshToken( const OAuthClientInfo& oauth_client_info, const std::string& refresh_token, const std::vector& scopes, int max_retries, Delegate* delegate) { return core_->RefreshToken(oauth_client_info, refresh_token, scopes, max_retries, delegate); } void GaiaOAuthClient::GetUserEmail(const std::string& access_token, int max_retries, Delegate* delegate) { return core_->GetUserEmail(access_token, max_retries, delegate); } void GaiaOAuthClient::GetUserId(const std::string& access_token, int max_retries, Delegate* delegate) { return core_->GetUserId(access_token, max_retries, delegate); } void GaiaOAuthClient::GetUserInfo(const std::string& access_token, int max_retries, Delegate* delegate) { return core_->GetUserInfo(access_token, max_retries, delegate); } void GaiaOAuthClient::GetTokenInfo(const std::string& access_token, int max_retries, Delegate* delegate) { return core_->GetTokenInfo("access_token", access_token, max_retries, delegate); } void GaiaOAuthClient::GetTokenHandleInfo(const std::string& token_handle, int max_retries, Delegate* delegate) { return core_->GetTokenInfo("token_handle", token_handle, max_retries, delegate); } void GaiaOAuthClient::GetAccountCapabilities( const std::string& oauth_access_token, const std::vector& capabilities_names, int max_retries, Delegate* delegate) { return core_->GetAccountCapabilities(oauth_access_token, capabilities_names, max_retries, delegate); } } // namespace gaia