1 // Copyright (c) 2012 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_API_H_ 6 #define CHROME_BROWSER_EXTENSIONS_API_IDENTITY_IDENTITY_API_H_ 7 8 #include <map> 9 #include <set> 10 #include <string> 11 #include <utility> 12 #include <vector> 13 14 #include "base/memory/ref_counted.h" 15 #include "base/memory/weak_ptr.h" 16 #include "base/observer_list.h" 17 #include "chrome/browser/extensions/api/identity/account_tracker.h" 18 #include "chrome/browser/extensions/api/identity/extension_token_key.h" 19 #include "chrome/browser/extensions/api/identity/gaia_web_auth_flow.h" 20 #include "chrome/browser/extensions/api/identity/identity_mint_queue.h" 21 #include "chrome/browser/extensions/api/identity/identity_signin_flow.h" 22 #include "chrome/browser/extensions/api/identity/web_auth_flow.h" 23 #include "chrome/browser/extensions/chrome_extension_function.h" 24 #include "chrome/browser/signin/signin_global_error.h" 25 #include "extensions/browser/browser_context_keyed_api_factory.h" 26 #include "google_apis/gaia/oauth2_mint_token_flow.h" 27 #include "google_apis/gaia/oauth2_token_service.h" 28 29 class GoogleServiceAuthError; 30 class MockGetAuthTokenFunction; 31 32 namespace content { 33 class BrowserContext; 34 } 35 36 namespace extensions { 37 38 class GetAuthTokenFunctionTest; 39 class MockGetAuthTokenFunction; 40 41 namespace identity_constants { 42 extern const char kInvalidClientId[]; 43 extern const char kInvalidScopes[]; 44 extern const char kAuthFailure[]; 45 extern const char kNoGrant[]; 46 extern const char kUserRejected[]; 47 extern const char kUserNotSignedIn[]; 48 extern const char kInteractionRequired[]; 49 extern const char kInvalidRedirect[]; 50 extern const char kOffTheRecord[]; 51 extern const char kPageLoadFailure[]; 52 extern const char kCanceled[]; 53 } // namespace identity_constants 54 55 class IdentityTokenCacheValue { 56 public: 57 IdentityTokenCacheValue(); 58 explicit IdentityTokenCacheValue(const IssueAdviceInfo& issue_advice); 59 IdentityTokenCacheValue(const std::string& token, 60 base::TimeDelta time_to_live); 61 ~IdentityTokenCacheValue(); 62 63 // Order of these entries is used to determine whether or not new 64 // entries supercede older ones in SetCachedToken. 65 enum CacheValueStatus { 66 CACHE_STATUS_NOTFOUND, 67 CACHE_STATUS_ADVICE, 68 CACHE_STATUS_TOKEN 69 }; 70 71 CacheValueStatus status() const; 72 const IssueAdviceInfo& issue_advice() const; 73 const std::string& token() const; 74 const base::Time& expiration_time() const; 75 76 private: 77 bool is_expired() const; 78 79 CacheValueStatus status_; 80 IssueAdviceInfo issue_advice_; 81 std::string token_; 82 base::Time expiration_time_; 83 }; 84 85 class IdentityAPI : public BrowserContextKeyedAPI, 86 public AccountTracker::Observer { 87 public: 88 typedef std::map<ExtensionTokenKey, IdentityTokenCacheValue> CachedTokens; 89 90 class ShutdownObserver { 91 public: 92 virtual void OnShutdown() = 0; 93 }; 94 95 explicit IdentityAPI(content::BrowserContext* context); 96 virtual ~IdentityAPI(); 97 98 // Request serialization queue for getAuthToken. 99 IdentityMintRequestQueue* mint_queue(); 100 101 // Token cache 102 void SetCachedToken(const ExtensionTokenKey& key, 103 const IdentityTokenCacheValue& token_data); 104 void EraseCachedToken(const std::string& extension_id, 105 const std::string& token); 106 void EraseAllCachedTokens(); 107 const IdentityTokenCacheValue& GetCachedToken(const ExtensionTokenKey& key); 108 109 const CachedTokens& GetAllCachedTokens(); 110 111 // Account queries. 112 std::vector<std::string> GetAccounts() const; 113 std::string FindAccountKeyByGaiaId(const std::string& gaia_id); 114 115 // Global error reporting. 116 void ReportAuthError(const GoogleServiceAuthError& error); 117 GoogleServiceAuthError GetAuthStatusForTest() const; 118 119 // BrowserContextKeyedAPI implementation. 120 virtual void Shutdown() OVERRIDE; 121 static BrowserContextKeyedAPIFactory<IdentityAPI>* GetFactoryInstance(); 122 123 // AccountTracker::Observer implementation: 124 virtual void OnAccountAdded(const AccountIds& ids) OVERRIDE; 125 virtual void OnAccountRemoved(const AccountIds& ids) OVERRIDE; 126 virtual void OnAccountSignInChanged(const AccountIds& ids, 127 bool is_signed_in) OVERRIDE; 128 129 void AddShutdownObserver(ShutdownObserver* observer); 130 void RemoveShutdownObserver(ShutdownObserver* observer); 131 132 void SetAccountStateForTest(AccountIds ids, bool is_signed_in); 133 134 private: 135 friend class BrowserContextKeyedAPIFactory<IdentityAPI>; 136 137 // BrowserContextKeyedAPI implementation. 138 static const char* service_name() { return "IdentityAPI"; } 139 static const bool kServiceIsNULLWhileTesting = true; 140 141 content::BrowserContext* browser_context_; 142 IdentityMintRequestQueue mint_queue_; 143 CachedTokens token_cache_; 144 AccountTracker account_tracker_; 145 ObserverList<ShutdownObserver> shutdown_observer_list_; 146 }; 147 148 template <> 149 void BrowserContextKeyedAPIFactory<IdentityAPI>::DeclareFactoryDependencies(); 150 151 class IdentityGetAccountsFunction : public ChromeUIThreadExtensionFunction { 152 public: 153 DECLARE_EXTENSION_FUNCTION("identity.getAccounts", 154 IDENTITY_GETACCOUNTS); 155 156 IdentityGetAccountsFunction(); 157 158 private: 159 virtual ~IdentityGetAccountsFunction(); 160 161 // UIThreadExtensionFunction implementation. 162 virtual ExtensionFunction::ResponseAction Run() OVERRIDE; 163 }; 164 165 // identity.getAuthToken fetches an OAuth 2 function for the 166 // caller. The request has three sub-flows: non-interactive, 167 // interactive, and sign-in. 168 // 169 // In the non-interactive flow, getAuthToken requests a token from 170 // GAIA. GAIA may respond with a token, an error, or "consent 171 // required". In the consent required cases, getAuthToken proceeds to 172 // the second, interactive phase. 173 // 174 // The interactive flow presents a scope approval dialog to the 175 // user. If the user approves the request, a grant will be recorded on 176 // the server, and an access token will be returned to the caller. 177 // 178 // In some cases we need to display a sign-in dialog. Normally the 179 // profile will be signed in already, but if it turns out we need a 180 // new login token, there is a sign-in flow. If that flow completes 181 // successfully, getAuthToken proceeds to the non-interactive flow. 182 class IdentityGetAuthTokenFunction : public ChromeAsyncExtensionFunction, 183 public GaiaWebAuthFlow::Delegate, 184 public IdentityMintRequestQueue::Request, 185 public OAuth2MintTokenFlow::Delegate, 186 public IdentitySigninFlow::Delegate, 187 public OAuth2TokenService::Consumer, 188 public IdentityAPI::ShutdownObserver { 189 public: 190 DECLARE_EXTENSION_FUNCTION("identity.getAuthToken", 191 EXPERIMENTAL_IDENTITY_GETAUTHTOKEN); 192 193 IdentityGetAuthTokenFunction(); 194 195 protected: 196 virtual ~IdentityGetAuthTokenFunction(); 197 198 // IdentitySigninFlow::Delegate implementation: 199 virtual void SigninSuccess() OVERRIDE; 200 virtual void SigninFailed() OVERRIDE; 201 202 // GaiaWebAuthFlow::Delegate implementation: 203 virtual void OnGaiaFlowFailure(GaiaWebAuthFlow::Failure failure, 204 GoogleServiceAuthError service_error, 205 const std::string& oauth_error) OVERRIDE; 206 virtual void OnGaiaFlowCompleted(const std::string& access_token, 207 const std::string& expiration) OVERRIDE; 208 209 // OAuth2TokenService::Consumer implementation: 210 virtual void OnGetTokenSuccess(const OAuth2TokenService::Request* request, 211 const std::string& access_token, 212 const base::Time& expiration_time) OVERRIDE; 213 virtual void OnGetTokenFailure(const OAuth2TokenService::Request* request, 214 const GoogleServiceAuthError& error) OVERRIDE; 215 216 scoped_ptr<OAuth2TokenService::Request> login_token_request_; 217 218 private: 219 FRIEND_TEST_ALL_PREFIXES(GetAuthTokenFunctionTest, 220 ComponentWithChromeClientId); 221 FRIEND_TEST_ALL_PREFIXES(GetAuthTokenFunctionTest, 222 ComponentWithNormalClientId); 223 FRIEND_TEST_ALL_PREFIXES(GetAuthTokenFunctionTest, InteractiveQueueShutdown); 224 FRIEND_TEST_ALL_PREFIXES(GetAuthTokenFunctionTest, NoninteractiveShutdown); 225 friend class MockGetAuthTokenFunction; 226 227 // ExtensionFunction: 228 virtual bool RunAsync() OVERRIDE; 229 230 // Helpers to report async function results to the caller. 231 void StartAsyncRun(); 232 void CompleteAsyncRun(bool success); 233 void CompleteFunctionWithResult(const std::string& access_token); 234 void CompleteFunctionWithError(const std::string& error); 235 236 // Initiate/complete the sub-flows. 237 void StartSigninFlow(); 238 void StartMintTokenFlow(IdentityMintRequestQueue::MintType type); 239 void CompleteMintTokenFlow(); 240 241 // IdentityMintRequestQueue::Request implementation: 242 virtual void StartMintToken(IdentityMintRequestQueue::MintType type) OVERRIDE; 243 244 // OAuth2MintTokenFlow::Delegate implementation: 245 virtual void OnMintTokenSuccess(const std::string& access_token, 246 int time_to_live) OVERRIDE; 247 virtual void OnMintTokenFailure( 248 const GoogleServiceAuthError& error) OVERRIDE; 249 virtual void OnIssueAdviceSuccess( 250 const IssueAdviceInfo& issue_advice) OVERRIDE; 251 252 // IdentityAPI::ShutdownObserver implementation: 253 virtual void OnShutdown() OVERRIDE; 254 255 // Starts a login access token request. 256 virtual void StartLoginAccessTokenRequest(); 257 258 #if defined(OS_CHROMEOS) 259 // Starts a login access token request for device robot account. This method 260 // will be called only in enterprise kiosk mode in ChromeOS. 261 virtual void StartDeviceLoginAccessTokenRequest(); 262 #endif 263 264 // Starts a mint token request to GAIA. 265 void StartGaiaRequest(const std::string& login_access_token); 266 267 // Methods for invoking UI. Overridable for testing. 268 virtual void ShowLoginPopup(); 269 virtual void ShowOAuthApprovalDialog(const IssueAdviceInfo& issue_advice); 270 // Caller owns the returned instance. 271 virtual OAuth2MintTokenFlow* CreateMintTokenFlow( 272 const std::string& login_access_token); 273 274 // Checks if there is a master login token to mint tokens for the extension. 275 virtual bool HasLoginToken() const; 276 277 // Maps OAuth2 protocol errors to an error message returned to the 278 // developer in chrome.runtime.lastError. 279 std::string MapOAuth2ErrorToDescription(const std::string& error); 280 281 std::string GetOAuth2ClientId() const; 282 283 bool should_prompt_for_scopes_; 284 IdentityMintRequestQueue::MintType mint_token_flow_type_; 285 scoped_ptr<OAuth2MintTokenFlow> mint_token_flow_; 286 OAuth2MintTokenFlow::Mode gaia_mint_token_mode_; 287 bool should_prompt_for_signin_; 288 289 scoped_ptr<ExtensionTokenKey> token_key_; 290 std::string oauth2_client_id_; 291 // When launched in interactive mode, and if there is no existing grant, 292 // a permissions prompt will be popped up to the user. 293 IssueAdviceInfo issue_advice_; 294 scoped_ptr<GaiaWebAuthFlow> gaia_web_auth_flow_; 295 scoped_ptr<IdentitySigninFlow> signin_flow_; 296 }; 297 298 class IdentityGetProfileUserInfoFunction 299 : public ChromeUIThreadExtensionFunction { 300 public: 301 DECLARE_EXTENSION_FUNCTION("identity.getProfileUserInfo", 302 IDENTITY_GETPROFILEUSERINFO); 303 304 IdentityGetProfileUserInfoFunction(); 305 306 private: 307 virtual ~IdentityGetProfileUserInfoFunction(); 308 309 // UIThreadExtensionFunction implementation. 310 virtual ExtensionFunction::ResponseAction Run() OVERRIDE; 311 }; 312 313 class IdentityRemoveCachedAuthTokenFunction 314 : public ChromeSyncExtensionFunction { 315 public: 316 DECLARE_EXTENSION_FUNCTION("identity.removeCachedAuthToken", 317 EXPERIMENTAL_IDENTITY_REMOVECACHEDAUTHTOKEN) 318 IdentityRemoveCachedAuthTokenFunction(); 319 320 protected: 321 virtual ~IdentityRemoveCachedAuthTokenFunction(); 322 323 // SyncExtensionFunction implementation: 324 virtual bool RunSync() OVERRIDE; 325 }; 326 327 class IdentityLaunchWebAuthFlowFunction : public ChromeAsyncExtensionFunction, 328 public WebAuthFlow::Delegate { 329 public: 330 DECLARE_EXTENSION_FUNCTION("identity.launchWebAuthFlow", 331 EXPERIMENTAL_IDENTITY_LAUNCHWEBAUTHFLOW); 332 333 IdentityLaunchWebAuthFlowFunction(); 334 335 // Tests may override extension_id. 336 void InitFinalRedirectURLPrefixForTest(const std::string& extension_id); 337 338 private: 339 virtual ~IdentityLaunchWebAuthFlowFunction(); 340 virtual bool RunAsync() OVERRIDE; 341 342 // WebAuthFlow::Delegate implementation. 343 virtual void OnAuthFlowFailure(WebAuthFlow::Failure failure) OVERRIDE; 344 virtual void OnAuthFlowURLChange(const GURL& redirect_url) OVERRIDE; 345 virtual void OnAuthFlowTitleChange(const std::string& title) OVERRIDE {} 346 347 // Helper to initialize final URL prefix. 348 void InitFinalRedirectURLPrefix(const std::string& extension_id); 349 350 scoped_ptr<WebAuthFlow> auth_flow_; 351 GURL final_url_prefix_; 352 }; 353 354 } // namespace extensions 355 356 #endif // CHROME_BROWSER_EXTENSIONS_API_IDENTITY_IDENTITY_API_H_ 357