1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef CHROME_BROWSER_EXTENSIONS_API_IDENTITY_IDENTITY_GET_AUTH_TOKEN_FUNCTION_H_
6 #define CHROME_BROWSER_EXTENSIONS_API_IDENTITY_IDENTITY_GET_AUTH_TOKEN_FUNCTION_H_
7 
8 #include <memory>
9 #include <set>
10 #include <string>
11 
12 #include "base/callback_list.h"
13 #include "base/memory/weak_ptr.h"
14 #include "base/optional.h"
15 #include "base/scoped_observer.h"
16 #include "chrome/browser/extensions/api/identity/gaia_remote_consent_flow.h"
17 #include "chrome/browser/extensions/api/identity/gaia_web_auth_flow.h"
18 #include "chrome/browser/extensions/api/identity/identity_mint_queue.h"
19 #include "components/signin/public/identity_manager/identity_manager.h"
20 #include "extensions/browser/extension_function.h"
21 #include "extensions/browser/extension_function_histogram_value.h"
22 #include "google_apis/gaia/google_service_auth_error.h"
23 #include "google_apis/gaia/oauth2_access_token_manager.h"
24 #include "google_apis/gaia/oauth2_mint_token_flow.h"
25 
26 namespace signin {
27 class AccessTokenFetcher;
28 struct AccessTokenInfo;
29 }  // namespace signin
30 
31 namespace extensions {
32 class IdentityGetAuthTokenError;
33 
34 // identity.getAuthToken fetches an OAuth 2 function for the
35 // caller. The request has three sub-flows: non-interactive,
36 // interactive, and sign-in.
37 //
38 // In the non-interactive flow, getAuthToken requests a token from
39 // GAIA. GAIA may respond with a token, an error, or "consent
40 // required". In the consent required cases, getAuthToken proceeds to
41 // the second, interactive phase.
42 //
43 // The interactive flow presents a scope approval dialog to the
44 // user. If the user approves the request, a grant will be recorded on
45 // the server, and an access token will be returned to the caller.
46 //
47 // In some cases we need to display a sign-in dialog. Normally the
48 // profile will be signed in already, but if it turns out we need a
49 // new login token, there is a sign-in flow. If that flow completes
50 // successfully, getAuthToken proceeds to the non-interactive flow.
51 class IdentityGetAuthTokenFunction : public ExtensionFunction,
52                                      public GaiaWebAuthFlow::Delegate,
53                                      public GaiaRemoteConsentFlow::Delegate,
54                                      public IdentityMintRequestQueue::Request,
55                                      public signin::IdentityManager::Observer,
56 #if defined(OS_CHROMEOS)
57                                      public OAuth2AccessTokenManager::Consumer,
58 #endif
59                                      public OAuth2MintTokenFlow::Delegate {
60  public:
61   DECLARE_EXTENSION_FUNCTION("identity.getAuthToken",
62                              EXPERIMENTAL_IDENTITY_GETAUTHTOKEN)
63 
64   IdentityGetAuthTokenFunction();
65 
GetExtensionTokenKeyForTest()66   const ExtensionTokenKey* GetExtensionTokenKeyForTest() { return &token_key_; }
67 
68   void OnIdentityAPIShutdown();
69 
70  protected:
71   ~IdentityGetAuthTokenFunction() override;
72 
73   void SigninFailed();
74 
75   // GaiaWebAuthFlow::Delegate implementation:
76   void OnGaiaFlowFailure(GaiaWebAuthFlow::Failure failure,
77                          GoogleServiceAuthError service_error,
78                          const std::string& oauth_error) override;
79   void OnGaiaFlowCompleted(const std::string& access_token,
80                            const std::string& expiration) override;
81 
82   // GaiaRemoteConsentFlow::Delegate implementation:
83   void OnGaiaRemoteConsentFlowFailed(
84       GaiaRemoteConsentFlow::Failure failure) override;
85   void OnGaiaRemoteConsentFlowApproved(const std::string& consent_result,
86                                        const std::string& gaia_id) override;
87 
88   // Starts a login access token request.
89   virtual void StartTokenKeyAccountAccessTokenRequest();
90 
91 // TODO(blundell): Investigate feasibility of moving the ChromeOS use case
92 // to use the Identity Service instead of being an
93 // OAuth2AccessTokenManager::Consumer.
94 #if defined(OS_CHROMEOS)
95   void OnGetTokenSuccess(
96       const OAuth2AccessTokenManager::Request* request,
97       const OAuth2AccessTokenConsumer::TokenResponse& token_response) override;
98   void OnGetTokenFailure(const OAuth2AccessTokenManager::Request* request,
99                          const GoogleServiceAuthError& error) override;
100 #endif
101 
102   void OnAccessTokenFetchCompleted(GoogleServiceAuthError error,
103                                    signin::AccessTokenInfo access_token_info);
104 
105   // Invoked on completion of the access token fetcher.
106   // Exposed for testing.
107   void OnGetAccessTokenComplete(const base::Optional<std::string>& access_token,
108                                 base::Time expiration_time,
109                                 const GoogleServiceAuthError& error);
110 
111   // Starts a mint token request to GAIA.
112   // Exposed for testing.
113   virtual void StartGaiaRequest(const std::string& login_access_token);
114 
115   // Caller owns the returned instance.
116   // Exposed for testing.
117   virtual std::unique_ptr<OAuth2MintTokenFlow> CreateMintTokenFlow();
118 
119   Profile* GetProfile() const;
120 
121   // Returns the gaia id of the account requested by or previously selected for
122   // this extension if the account is available on the device. Otherwise,
123   // returns an empty string.
124   // Exposed for testing.
125   std::string GetSelectedUserId() const;
126 
127   // Pending request for an access token from the device account (via
128   // DeviceOAuth2TokenService).
129   std::unique_ptr<OAuth2AccessTokenManager::Request>
130       device_access_token_request_;
131 
132   // Pending fetcher for an access token for |token_key_.account_id| (via
133   // IdentityManager).
134   std::unique_ptr<signin::AccessTokenFetcher>
135       token_key_account_access_token_fetcher_;
136 
137   // Returns whether granular permissions will be requested.
138   // Exposed for testing.
139   bool enable_granular_permissions() const;
140 
141  private:
142   FRIEND_TEST_ALL_PREFIXES(GetAuthTokenFunctionTest,
143                            ComponentWithChromeClientId);
144   FRIEND_TEST_ALL_PREFIXES(GetAuthTokenFunctionTest,
145                            ComponentWithNormalClientId);
146   FRIEND_TEST_ALL_PREFIXES(GetAuthTokenFunctionTest, InteractiveQueueShutdown);
147   FRIEND_TEST_ALL_PREFIXES(GetAuthTokenFunctionTest, NoninteractiveShutdown);
148 
149   // Request the primary account info.
150   // |extension_gaia_id|: The GAIA ID that was set in the parameters for this
151   // instance, or empty if this was not in the parameters.
152   void GetAuthTokenForPrimaryAccount(const std::string& extension_gaia_id);
153 
154   // Wrapper to FindExtendedAccountInfoForAccountWithRefreshTokenByGaiaId() to
155   // avoid a synchronous call to IdentityManager in RunAsync().
156   void FetchExtensionAccountInfo(const std::string& gaia_id);
157 
158   // Called when the AccountInfo that this instance should use is available.
159   void OnReceivedExtensionAccountInfo(const CoreAccountInfo* account_info);
160 
161   // signin::IdentityManager::Observer implementation:
162   void OnRefreshTokenUpdatedForAccount(
163       const CoreAccountInfo& account_info) override;
164   void OnAccountsInCookieUpdated(
165       const signin::AccountsInCookieJarInfo& accounts_in_cookie_jar_info,
166       const GoogleServiceAuthError& error) override;
167   void OnPrimaryAccountSet(
168       const CoreAccountInfo& primary_account_info) override;
169 
170   // Attempts to show the signin UI after the service auth error if this error
171   // isn't transient.
172   // Returns true iff the signin flow was triggered.
173   bool TryRecoverFromServiceAuthError(const GoogleServiceAuthError& error);
174 
175   // ExtensionFunction:
176   ResponseAction Run() override;
177 
178   // Helpers to report async function results to the caller.
179   void StartAsyncRun();
180   void CompleteAsyncRun(ResponseValue response);
181   void CompleteFunctionWithResult(const std::string& access_token,
182                                   const std::set<std::string>& granted_scopes);
183   void CompleteFunctionWithError(const IdentityGetAuthTokenError& error);
184 
185   // Whether a signin flow should be initiated in the user's current state.
186   bool ShouldStartSigninFlow();
187 
188   // Initiate/complete the sub-flows.
189   void StartSigninFlow();
190   void StartMintTokenFlow(IdentityMintRequestQueue::MintType type);
191   void CompleteMintTokenFlow();
192 
193   // IdentityMintRequestQueue::Request implementation:
194   void StartMintToken(IdentityMintRequestQueue::MintType type) override;
195 
196   // OAuth2MintTokenFlow::Delegate implementation:
197   void OnMintTokenSuccess(const std::string& access_token,
198                           const std::set<std::string>& granted_scopes,
199                           int time_to_live) override;
200   void OnMintTokenFailure(const GoogleServiceAuthError& error) override;
201   void OnIssueAdviceSuccess(const IssueAdviceInfo& issue_advice) override;
202   void OnRemoteConsentSuccess(
203       const RemoteConsentResolutionData& resolution_data) override;
204 
205 #if defined(OS_CHROMEOS)
206   // Starts a login access token request for device robot account. This method
207   // will be called only in Chrome OS for:
208   // 1. Enterprise kiosk mode.
209   // 2. Allowlisted first party apps in public session.
210   virtual void StartDeviceAccessTokenRequest();
211 
212   bool IsOriginAllowlistedInPublicSession();
213 #endif
214 
215   // Methods for invoking UI. Overridable for testing.
216   virtual void ShowExtensionLoginPrompt();
217   virtual void ShowOAuthApprovalDialog(const IssueAdviceInfo& issue_advice);
218   virtual void ShowRemoteConsentDialog(
219       const RemoteConsentResolutionData& resolution_data);
220 
221   // Checks if there is a master login token to mint tokens for the extension.
222   bool HasRefreshTokenForTokenKeyAccount() const;
223 
224   std::string GetOAuth2ClientId() const;
225 
226   // Returns true if extensions are restricted to the primary account.
227   bool IsPrimaryAccountOnly() const;
228 
229   bool interactive_ = false;
230   bool should_prompt_for_scopes_ = false;
231   IdentityMintRequestQueue::MintType mint_token_flow_type_;
232   std::unique_ptr<OAuth2MintTokenFlow> mint_token_flow_;
233   OAuth2MintTokenFlow::Mode gaia_mint_token_mode_;
234   bool should_prompt_for_signin_ = false;
235   bool enable_granular_permissions_ = false;
236 
237   // The gaia id of the account requested by or previously selected for this
238   // extension.
239   std::string selected_gaia_id_;
240 
241   // Shown in the extension login prompt.
242   std::string email_for_default_web_account_;
243 
244   ExtensionTokenKey token_key_{/*extension_id=*/"",
245                                /*account_info=*/CoreAccountInfo(),
246                                /*scopes=*/{}};
247   std::string oauth2_client_id_;
248   // When launched in interactive mode, and if there is no existing grant,
249   // a permissions prompt will be popped up to the user.
250   IssueAdviceInfo issue_advice_;
251   std::unique_ptr<GaiaWebAuthFlow> gaia_web_auth_flow_;
252   // The browser resolution consent flow.
253   RemoteConsentResolutionData resolution_data_;
254   std::unique_ptr<GaiaRemoteConsentFlow> gaia_remote_consent_flow_;
255   std::string consent_result_;
256   // Added for debugging https://crbug.com/1091423.
257   bool remote_consent_approved_ = false;
258 
259   // Invoked when IdentityAPI is shut down.
260   std::unique_ptr<base::OnceCallbackList<void()>::Subscription>
261       identity_api_shutdown_subscription_;
262 
263   ScopedObserver<signin::IdentityManager, signin::IdentityManager::Observer>
264       scoped_identity_manager_observer_{this};
265 
266   // This class can be listening to account changes, but only for one type of
267   // events at a time.
268   enum class AccountListeningMode {
269     kNotListening,            // Not listening account changes
270     kListeningCookies,        // Listening cookie changes
271     kListeningTokens,         // Listening token changes
272     kListeningPrimaryAccount  // Listening primary account changes
273   };
274   AccountListeningMode account_listening_mode_ =
275       AccountListeningMode::kNotListening;
276 
277   base::WeakPtrFactory<IdentityGetAuthTokenFunction> weak_ptr_factory_{this};
278 };
279 
280 }  // namespace extensions
281 
282 #endif  // CHROME_BROWSER_EXTENSIONS_API_IDENTITY_IDENTITY_GET_AUTH_TOKEN_FUNCTION_H_
283