|
| 1 | +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 |
| 2 | +index 9f9e9eb07044c5ed1a9655a25b365c4cd7c21f43..84a2c30b089d1529c66d3463dfb9d00f4af664ae 100644 |
| 3 | +--- a/chrome/browser/extensions/api/identity/identity_get_auth_token_function.cc |
| 4 | ++++ b/chrome/browser/extensions/api/identity/identity_get_auth_token_function.cc |
| 5 | +@@ -4,6 +4,8 @@ |
| 6 | + |
| 7 | + #include "chrome/browser/extensions/api/identity/identity_get_auth_token_function.h" |
| 8 | + |
| 9 | ++#include <algorithm> |
| 10 | ++ |
| 11 | + #include <set> |
| 12 | + #include <vector> |
| 13 | + |
| 14 | +@@ -46,6 +48,13 @@ |
| 15 | + #include "google_apis/gaia/gaia_urls.h" |
| 16 | + #include "services/network/public/cpp/shared_url_loader_factory.h" |
| 17 | + |
| 18 | ++#include "base/compiler_specific.h" |
| 19 | ++#include "base/strings/string_split.h" |
| 20 | ++#include "base/strings/string_util.h" |
| 21 | ++#include "google_apis/google_api_keys.h" |
| 22 | ++#include "net/base/url_util.h" |
| 23 | ++ |
| 24 | ++ |
| 25 | + #if BUILDFLAG(IS_CHROMEOS_ASH) |
| 26 | + #include "chrome/browser/app_mode/app_mode_utils.h" |
| 27 | + #include "chrome/browser/ash/login/session/user_session_manager.h" |
| 28 | +@@ -174,6 +183,14 @@ ExtensionFunction::ResponseAction IdentityGetAuthTokenFunction::Run() { |
| 29 | + // From here on out, results must be returned asynchronously. |
| 30 | + StartAsyncRun(); |
| 31 | + |
| 32 | ++ // Use the embedded Google OAuth flow only if Google Chrome API key is used. |
| 33 | ++ // Otherwise, fallback to the web OAuth flow. |
| 34 | ++ if (!google_apis::IsGoogleChromeAPIKeyUsed()) { |
| 35 | ++ // Start mint token flow with an empty account id. |
| 36 | ++ StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE); |
| 37 | ++ return RespondLater(); |
| 38 | ++ } |
| 39 | ++ |
| 40 | + if (gaia_id.empty() || IsPrimaryAccountOnly()) { |
| 41 | + // Try the primary account. |
| 42 | + // TODO(https://crbug.com/932400): collapse the asynchronicity |
| 43 | +@@ -407,11 +424,17 @@ void IdentityGetAuthTokenFunction::StartSigninFlow() { |
| 44 | + |
| 45 | + void IdentityGetAuthTokenFunction::StartMintTokenFlow( |
| 46 | + IdentityMintRequestQueue::MintType type) { |
| 47 | ++// ChromeOS in kiosk mode may start the mint token flow without account. |
| 48 | + #if !BUILDFLAG(IS_CHROMEOS_ASH) |
| 49 | +- // ChromeOS in kiosk mode may start the mint token flow without account. |
| 50 | +- DCHECK(!token_key_.account_info.IsEmpty()); |
| 51 | +- DCHECK(IdentityManagerFactory::GetForProfile(GetProfile()) |
| 52 | +- ->HasAccountWithRefreshToken(token_key_.account_info.account_id)); |
| 53 | ++ if (!google_apis::IsGoogleChromeAPIKeyUsed()) { |
| 54 | ++ // Web Auth flow doesn't use an account. |
| 55 | ++ DCHECK(token_key_.account_info.IsEmpty()); |
| 56 | ++ } else { |
| 57 | ++ DCHECK(!token_key_.account_info.IsEmpty()); |
| 58 | ++ DCHECK( |
| 59 | ++ IdentityManagerFactory::GetForProfile(GetProfile()) |
| 60 | ++ ->HasAccountWithRefreshToken(token_key_.account_info.account_id)); |
| 61 | ++ } |
| 62 | + #endif |
| 63 | + TRACE_EVENT_NESTABLE_ASYNC_BEGIN1("identity", "MintTokenFlow", this, "type", |
| 64 | + type); |
| 65 | +@@ -497,6 +520,14 @@ void IdentityGetAuthTokenFunction::StartMintToken( |
| 66 | + } |
| 67 | + #endif |
| 68 | + |
| 69 | ++ if (!google_apis::IsGoogleChromeAPIKeyUsed()) { |
| 70 | ++ // Fallback to the web OAuth flow that can only be completed in |
| 71 | ++ // interactive mode. |
| 72 | ++ CompleteMintTokenFlow(); |
| 73 | ++ StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE); |
| 74 | ++ return; |
| 75 | ++ } |
| 76 | ++ |
| 77 | + if (oauth2_info.auto_approve && *oauth2_info.auto_approve) { |
| 78 | + // oauth2_info.auto_approve is protected by an allowlist in |
| 79 | + // _manifest_features.json hence only selected extensions take |
| 80 | +@@ -539,6 +570,11 @@ void IdentityGetAuthTokenFunction::StartMintToken( |
| 81 | + cache_entry.granted_scopes()); |
| 82 | + break; |
| 83 | + case IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND: |
| 84 | ++ if (!google_apis::IsGoogleChromeAPIKeyUsed()) { |
| 85 | ++ StartWebAuthFlow(); |
| 86 | ++ return; |
| 87 | ++ } |
| 88 | ++ FALLTHROUGH; |
| 89 | + case IdentityTokenCacheValue::CACHE_STATUS_REMOTE_CONSENT: |
| 90 | + ShowRemoteConsentDialog(resolution_data_); |
| 91 | + break; |
| 92 | +@@ -613,6 +649,33 @@ void IdentityGetAuthTokenFunction::OnRemoteConsentSuccess( |
| 93 | + StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE); |
| 94 | + } |
| 95 | + |
| 96 | ++void IdentityGetAuthTokenFunction::StartWebAuthFlow() { |
| 97 | ++ // Compute the reverse DNS notation of the client ID and use it as a custom |
| 98 | ++ // URI scheme. |
| 99 | ++ std::vector<std::string> client_id_components = base::SplitString( |
| 100 | ++ oauth2_client_id_, ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
| 101 | ++ std::reverse(client_id_components.begin(), client_id_components.end()); |
| 102 | ++ GURL redirect_url = GURL(base::JoinString(client_id_components, ".") + ":/"); |
| 103 | ++ redirect_scheme_ = redirect_url.scheme(); |
| 104 | ++ GURL google_oauth_url = GURL("https://accounts.google.com/o/oauth2/v2/auth"); |
| 105 | ++ google_oauth_url = net::AppendQueryParameter(google_oauth_url, "client_id", |
| 106 | ++ oauth2_client_id_); |
| 107 | ++ google_oauth_url = net::AppendQueryParameter(google_oauth_url, "redirect_uri", |
| 108 | ++ redirect_url.spec()); |
| 109 | ++ google_oauth_url = |
| 110 | ++ net::AppendQueryParameter(google_oauth_url, "response_type", "token"); |
| 111 | ++ google_oauth_url = net::AppendQueryParameter( |
| 112 | ++ google_oauth_url, "scope", |
| 113 | ++ base::JoinString(std::vector<std::string>(token_key_.scopes.begin(), |
| 114 | ++ token_key_.scopes.end()), |
| 115 | ++ " ")); |
| 116 | ++ web_auth_flow_ = std::make_unique<WebAuthFlow>( |
| 117 | ++ this, GetProfile(), google_oauth_url, |
| 118 | ++ interactive_ ? WebAuthFlow::INTERACTIVE : WebAuthFlow::SILENT, |
| 119 | ++ WebAuthFlow::LAUNCH_WEB_AUTH_FLOW); |
| 120 | ++ web_auth_flow_->Start(); |
| 121 | ++} |
| 122 | ++ |
| 123 | + void IdentityGetAuthTokenFunction::OnRefreshTokenUpdatedForAccount( |
| 124 | + const CoreAccountInfo& account_info) { |
| 125 | + if (account_listening_mode_ != AccountListeningMode::kListeningTokens) |
| 126 | +@@ -762,6 +825,88 @@ void IdentityGetAuthTokenFunction::OnGaiaRemoteConsentFlowApproved( |
| 127 | + StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE); |
| 128 | + } |
| 129 | + |
| 130 | ++void IdentityGetAuthTokenFunction::OnAuthFlowFailure( |
| 131 | ++ WebAuthFlow::Failure failure) { |
| 132 | ++ IdentityGetAuthTokenError error; |
| 133 | ++ switch (failure) { |
| 134 | ++ case WebAuthFlow::WINDOW_CLOSED: |
| 135 | ++ error = IdentityGetAuthTokenError( |
| 136 | ++ IdentityGetAuthTokenError::State::kRemoteConsentFlowRejected); |
| 137 | ++ break; |
| 138 | ++ case WebAuthFlow::INTERACTION_REQUIRED: |
| 139 | ++ error = IdentityGetAuthTokenError( |
| 140 | ++ IdentityGetAuthTokenError::State::kGaiaConsentInteractionRequired); |
| 141 | ++ break; |
| 142 | ++ case WebAuthFlow::LOAD_FAILED: |
| 143 | ++ error = IdentityGetAuthTokenError( |
| 144 | ++ IdentityGetAuthTokenError::State::kRemoteConsentPageLoadFailure); |
| 145 | ++ break; |
| 146 | ++ default: |
| 147 | ++ NOTREACHED() << "Unexpected error from web auth flow: " << failure; |
| 148 | ++ break; |
| 149 | ++ } |
| 150 | ++ if (web_auth_flow_) |
| 151 | ++ web_auth_flow_.release()->DetachDelegateAndDelete(); |
| 152 | ++ CompleteMintTokenFlow(); |
| 153 | ++ CompleteFunctionWithError(error); |
| 154 | ++} |
| 155 | ++ |
| 156 | ++void IdentityGetAuthTokenFunction::OnAuthFlowURLChange( |
| 157 | ++ const GURL& redirect_url) { |
| 158 | ++ if (!redirect_url.SchemeIs(redirect_scheme_)) |
| 159 | ++ return; |
| 160 | ++ |
| 161 | ++ if (web_auth_flow_) |
| 162 | ++ web_auth_flow_.release()->DetachDelegateAndDelete(); |
| 163 | ++ |
| 164 | ++ CompleteMintTokenFlow(); |
| 165 | ++ |
| 166 | ++ base::StringPairs response_pairs; |
| 167 | ++ if (!base::SplitStringIntoKeyValuePairs(redirect_url.ref(), '=', '&', |
| 168 | ++ &response_pairs)) { |
| 169 | ++ CompleteFunctionWithError( |
| 170 | ++ IdentityGetAuthTokenError(IdentityGetAuthTokenError::State::kNoGrant)); |
| 171 | ++ return; |
| 172 | ++ } |
| 173 | ++ |
| 174 | ++ auto access_token_it = |
| 175 | ++ std::find_if(response_pairs.begin(), response_pairs.end(), |
| 176 | ++ [](const auto& key_value_pair) { |
| 177 | ++ return key_value_pair.first == "access_token"; |
| 178 | ++ }); |
| 179 | ++ if (access_token_it == response_pairs.end()) { |
| 180 | ++ CompleteFunctionWithError( |
| 181 | ++ IdentityGetAuthTokenError(IdentityGetAuthTokenError::State::kNoGrant)); |
| 182 | ++ return; |
| 183 | ++ } |
| 184 | ++ std::string access_token = access_token_it->second; |
| 185 | ++ |
| 186 | ++ auto expires_in_it = |
| 187 | ++ std::find_if(response_pairs.begin(), response_pairs.end(), |
| 188 | ++ [](const auto& key_value_pair) { |
| 189 | ++ return key_value_pair.first == "expires_in"; |
| 190 | ++ }); |
| 191 | ++ int time_to_live_seconds; |
| 192 | ++ if (expires_in_it == response_pairs.end() || |
| 193 | ++ !base::StringToInt(expires_in_it->second, &time_to_live_seconds)) { |
| 194 | ++ CompleteFunctionWithError( |
| 195 | ++ IdentityGetAuthTokenError(IdentityGetAuthTokenError::State::kNoGrant)); |
| 196 | ++ return; |
| 197 | ++ } |
| 198 | ++ |
| 199 | ++ // `token_key_` doesn't have information about the account being used, so only |
| 200 | ++ // the last used token will be cached. |
| 201 | ++ IdentityTokenCacheValue token = IdentityTokenCacheValue::CreateToken( |
| 202 | ++ access_token, token_key_.scopes, |
| 203 | ++ base::TimeDelta::FromSeconds(time_to_live_seconds)); |
| 204 | ++ IdentityAPI::GetFactoryInstance() |
| 205 | ++ ->Get(GetProfile()) |
| 206 | ++ ->token_cache() |
| 207 | ++ ->SetToken(token_key_, token); |
| 208 | ++ |
| 209 | ++ CompleteFunctionWithResult(access_token, token_key_.scopes); |
| 210 | ++} |
| 211 | ++ |
| 212 | + void IdentityGetAuthTokenFunction::OnGetAccessTokenComplete( |
| 213 | + const absl::optional<std::string>& access_token, |
| 214 | + base::Time expiration_time, |
0 commit comments