Skip to content

Commit c7d6088

Browse files
authored
Merge pull request #14069 from brave/brave-15754
Support 3p APIs in chrome.identity without Google API keys
2 parents ca9cf1f + 9b9b01a commit c7d6088

3 files changed

+272
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/* Copyright (c) 2022 The Brave Authors. All rights reserved.
2+
* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
4+
* You can obtain one at http://mozilla.org/MPL/2.0/. */
5+
6+
#include "base/time/time.h"
7+
8+
#define FromSeconds \
9+
Max().is_max() ? base::Seconds(time_to_live_seconds) : base::Seconds
10+
#define FALLTHROUGH [[fallthrough]]
11+
#include "src/chrome/browser/extensions/api/identity/identity_get_auth_token_function.cc"
12+
#undef FALLTHROUGH
13+
#undef FromSeconds
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
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,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
diff --git a/chrome/browser/extensions/api/identity/identity_get_auth_token_function.h b/chrome/browser/extensions/api/identity/identity_get_auth_token_function.h
2+
index dc9259349ea3ddde10a6bee547f3f030061fdaa0..a8456d6ae2c06a62486f6c092526baa38ac573e9 100644
3+
--- a/chrome/browser/extensions/api/identity/identity_get_auth_token_function.h
4+
+++ b/chrome/browser/extensions/api/identity/identity_get_auth_token_function.h
5+
@@ -50,6 +50,7 @@ class IdentityGetAuthTokenError;
6+
// successfully, getAuthToken proceeds to the non-interactive flow.
7+
class IdentityGetAuthTokenFunction : public ExtensionFunction,
8+
public GaiaRemoteConsentFlow::Delegate,
9+
+ public WebAuthFlow::Delegate,
10+
public IdentityMintRequestQueue::Request,
11+
public signin::IdentityManager::Observer,
12+
#if BUILDFLAG(IS_CHROMEOS_ASH)
13+
@@ -77,6 +78,11 @@ class IdentityGetAuthTokenFunction : public ExtensionFunction,
14+
void OnGaiaRemoteConsentFlowApproved(const std::string& consent_result,
15+
const std::string& gaia_id) override;
16+
17+
+ // Used only if Google API keys aren't set up.
18+
+ // WebAuthFlow::Delegate implementation:
19+
+ void OnAuthFlowFailure(WebAuthFlow::Failure failure) override;
20+
+ void OnAuthFlowURLChange(const GURL& redirect_url) override;
21+
+
22+
// Starts a login access token request.
23+
virtual void StartTokenKeyAccountAccessTokenRequest();
24+
25+
@@ -193,6 +199,9 @@ class IdentityGetAuthTokenFunction : public ExtensionFunction,
26+
void OnRemoteConsentSuccess(
27+
const RemoteConsentResolutionData& resolution_data) override;
28+
29+
+ // Used only if Google API keys aren't set up.
30+
+ void StartWebAuthFlow();
31+
+
32+
#if BUILDFLAG(IS_CHROMEOS_ASH)
33+
// Starts a login access token request for device robot account. This method
34+
// will be called only in Chrome OS for:
35+
@@ -241,6 +250,10 @@ class IdentityGetAuthTokenFunction : public ExtensionFunction,
36+
// Added for debugging https://crbug.com/1091423.
37+
bool remote_consent_approved_ = false;
38+
39+
+ // Used only if Google API keys aren't set up.
40+
+ std::unique_ptr<WebAuthFlow> web_auth_flow_;
41+
+ std::string redirect_scheme_;
42+
+
43+
// Invoked when IdentityAPI is shut down.
44+
base::CallbackListSubscription identity_api_shutdown_subscription_;
45+

0 commit comments

Comments
 (0)