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