Files
brave-core/patches/chrome-browser-extensions-api-identity-identity_get_auth_token_function.cc.patch
T

199 lines
8.3 KiB
Diff

diff --git a/chrome/browser/extensions/api/identity/identity_get_auth_token_function.cc b/chrome/browser/extensions/api/identity/identity_get_auth_token_function.cc
index 6c3c4f1cb8f15f1f498f3c38f42ef9b2a17f8c25..fe97c5b0cb4d17ce63b59309a5dba9621471eb3e 100644
--- a/chrome/browser/extensions/api/identity/identity_get_auth_token_function.cc
+++ b/chrome/browser/extensions/api/identity/identity_get_auth_token_function.cc
@@ -44,6 +44,13 @@
#include "google_apis/gaia/gaia_constants.h"
#endif
+#include "base/compiler_specific.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "google_apis/google_api_keys.h"
+#include "net/base/url_util.h"
+#include "base/ranges/algorithm.h"
+
namespace extensions {
namespace {
@@ -155,6 +162,14 @@ ExtensionFunction::ResponseAction IdentityGetAuthTokenFunction::Run() {
// From here on out, results must be returned asynchronously.
StartAsyncRun();
+ // Use the embedded Google OAuth flow only if Google Chrome API key is used.
+ // Otherwise, fallback to the web OAuth flow.
+ if (!google_apis::IsGoogleChromeAPIKeyUsed()) {
+ // Start mint token flow with an empty account id.
+ StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE);
+ return RespondLater();
+ }
+
if (gaia_id.empty() || IsPrimaryAccountOnly()) {
// Try the primary account.
// TODO(https://crbug.com/932400): collapse the asynchronicity
@@ -381,9 +396,15 @@ void IdentityGetAuthTokenFunction::StartMintTokenFlow(
IdentityMintRequestQueue::MintType type) {
#if !BUILDFLAG(IS_CHROMEOS)
// ChromeOS in kiosk mode may start the mint token flow without account.
- DCHECK(!token_key_.account_info.IsEmpty());
- DCHECK(IdentityManagerFactory::GetForProfile(GetProfile())
- ->HasAccountWithRefreshToken(token_key_.account_info.account_id));
+ if (!google_apis::IsGoogleChromeAPIKeyUsed()) {
+ // Web Auth flow doesn't use an account.
+ DCHECK(token_key_.account_info.IsEmpty());
+ } else {
+ DCHECK(!token_key_.account_info.IsEmpty());
+ DCHECK(
+ IdentityManagerFactory::GetForProfile(GetProfile())
+ ->HasAccountWithRefreshToken(token_key_.account_info.account_id));
+ }
#endif
TRACE_EVENT_NESTABLE_ASYNC_BEGIN1("identity", "MintTokenFlow", this, "type",
type);
@@ -464,6 +485,14 @@ void IdentityGetAuthTokenFunction::StartMintToken(
}
#endif
+ if (!google_apis::IsGoogleChromeAPIKeyUsed()) {
+ // Fallback to the web OAuth flow that can only be completed in
+ // interactive mode.
+ CompleteMintTokenFlow();
+ StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE);
+ return;
+ }
+
if (oauth2_info.auto_approve && *oauth2_info.auto_approve) {
// oauth2_info.auto_approve is protected by an allowlist in
// _manifest_features.json hence only selected extensions take
@@ -506,6 +535,11 @@ void IdentityGetAuthTokenFunction::StartMintToken(
cache_entry.granted_scopes());
break;
case IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND:
+ if (!google_apis::IsGoogleChromeAPIKeyUsed()) {
+ StartWebAuthFlow();
+ return;
+ }
+ FALLTHROUGH;
case IdentityTokenCacheValue::CACHE_STATUS_REMOTE_CONSENT:
ShowRemoteConsentDialog(resolution_data_);
break;
@@ -580,6 +614,33 @@ void IdentityGetAuthTokenFunction::OnRemoteConsentSuccess(
StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE);
}
+void IdentityGetAuthTokenFunction::StartWebAuthFlow() {
+ // Compute the reverse DNS notation of the client ID and use it as a custom
+ // URI scheme.
+ std::vector<std::string> client_id_components = base::SplitString(
+ oauth2_client_id_, ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+ std::reverse(client_id_components.begin(), client_id_components.end());
+ GURL redirect_url = GURL(base::JoinString(client_id_components, ".") + ":/");
+ redirect_scheme_ = redirect_url.scheme();
+ GURL google_oauth_url = GURL("https://accounts.google.com/o/oauth2/v2/auth");
+ google_oauth_url = net::AppendQueryParameter(google_oauth_url, "client_id",
+ oauth2_client_id_);
+ google_oauth_url = net::AppendQueryParameter(google_oauth_url, "redirect_uri",
+ redirect_url.spec());
+ google_oauth_url =
+ net::AppendQueryParameter(google_oauth_url, "response_type", "token");
+ google_oauth_url = net::AppendQueryParameter(
+ google_oauth_url, "scope",
+ base::JoinString(std::vector<std::string>(token_key_.scopes.begin(),
+ token_key_.scopes.end()),
+ " "));
+ web_auth_flow_ = std::make_unique<WebAuthFlow>(
+ this, GetProfile(), google_oauth_url,
+ interactive_ ? WebAuthFlow::INTERACTIVE : WebAuthFlow::SILENT,
+ WebAuthFlow::LAUNCH_WEB_AUTH_FLOW);
+ web_auth_flow_->Start();
+}
+
void IdentityGetAuthTokenFunction::OnRefreshTokenUpdatedForAccount(
const CoreAccountInfo& account_info) {
if (account_listening_mode_ != AccountListeningMode::kListeningTokens)
@@ -730,6 +791,84 @@ void IdentityGetAuthTokenFunction::OnGaiaRemoteConsentFlowApproved(
StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE);
}
+void IdentityGetAuthTokenFunction::OnAuthFlowFailure(
+ WebAuthFlow::Failure failure) {
+ IdentityGetAuthTokenError error;
+ switch (failure) {
+ case WebAuthFlow::WINDOW_CLOSED:
+ error = IdentityGetAuthTokenError(
+ IdentityGetAuthTokenError::State::kRemoteConsentFlowRejected);
+ break;
+ case WebAuthFlow::INTERACTION_REQUIRED:
+ error = IdentityGetAuthTokenError(
+ IdentityGetAuthTokenError::State::kGaiaConsentInteractionRequired);
+ break;
+ case WebAuthFlow::LOAD_FAILED:
+ error = IdentityGetAuthTokenError(
+ IdentityGetAuthTokenError::State::kRemoteConsentPageLoadFailure);
+ break;
+ default:
+ NOTREACHED() << "Unexpected error from web auth flow: " << failure;
+ break;
+ }
+ if (web_auth_flow_)
+ web_auth_flow_.release()->DetachDelegateAndDelete();
+ CompleteMintTokenFlow();
+ CompleteFunctionWithError(error);
+}
+
+void IdentityGetAuthTokenFunction::OnAuthFlowURLChange(
+ const GURL& redirect_url) {
+ if (!redirect_url.SchemeIs(redirect_scheme_))
+ return;
+
+ if (web_auth_flow_)
+ web_auth_flow_.release()->DetachDelegateAndDelete();
+
+ CompleteMintTokenFlow();
+
+ base::StringPairs response_pairs;
+ if (!base::SplitStringIntoKeyValuePairs(redirect_url.ref(), '=', '&',
+ &response_pairs)) {
+ CompleteFunctionWithError(
+ IdentityGetAuthTokenError(IdentityGetAuthTokenError::State::kNoGrant));
+ return;
+ }
+
+ auto access_token_it =
+ base::ranges::find(response_pairs, "access_token",
+ &std::pair<std::string, std::string>::first);
+ if (access_token_it == response_pairs.end()) {
+ CompleteFunctionWithError(
+ IdentityGetAuthTokenError(IdentityGetAuthTokenError::State::kNoGrant));
+ return;
+ }
+ std::string access_token = access_token_it->second;
+
+ auto expires_in_it =
+ base::ranges::find(response_pairs, "expires_in",
+ &std::pair<std::string, std::string>::first);
+ int time_to_live_seconds;
+ if (expires_in_it == response_pairs.end() ||
+ !base::StringToInt(expires_in_it->second, &time_to_live_seconds)) {
+ CompleteFunctionWithError(
+ IdentityGetAuthTokenError(IdentityGetAuthTokenError::State::kNoGrant));
+ return;
+ }
+
+ // `token_key_` doesn't have information about the account being used, so only
+ // the last used token will be cached.
+ IdentityTokenCacheValue token = IdentityTokenCacheValue::CreateToken(
+ access_token, token_key_.scopes,
+ base::TimeDelta::FromSeconds(time_to_live_seconds));
+ IdentityAPI::GetFactoryInstance()
+ ->Get(GetProfile())
+ ->token_cache()
+ ->SetToken(token_key_, token);
+
+ CompleteFunctionWithResult(access_token, token_key_.scopes);
+}
+
void IdentityGetAuthTokenFunction::OnGetAccessTokenComplete(
const absl::optional<std::string>& access_token,
base::Time expiration_time,