Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / api / identity / identity_api.cc
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 #include "chrome/browser/extensions/api/identity/identity_api.h"
6
7 #include <set>
8 #include <string>
9 #include <utility>
10 #include <vector>
11
12 #include "base/lazy_instance.h"
13 #include "base/prefs/pref_service.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/strings/stringprintf.h"
16 #include "base/values.h"
17 #include "chrome/browser/app_mode/app_mode_utils.h"
18 #include "chrome/browser/browser_process.h"
19 #include "chrome/browser/chrome_notification_types.h"
20 #include "chrome/browser/extensions/extension_service.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
23 #include "chrome/browser/signin/signin_global_error.h"
24 #include "chrome/browser/signin/signin_manager_factory.h"
25 #include "chrome/common/extensions/api/identity.h"
26 #include "chrome/common/extensions/api/identity/oauth2_manifest_handler.h"
27 #include "chrome/common/pref_names.h"
28 #include "chrome/common/url_constants.h"
29 #include "components/signin/core/browser/profile_oauth2_token_service.h"
30 #include "components/signin/core/browser/signin_manager.h"
31 #include "extensions/browser/event_router.h"
32 #include "extensions/browser/extension_function_dispatcher.h"
33 #include "extensions/common/extension.h"
34 #include "google_apis/gaia/gaia_urls.h"
35 #include "url/gurl.h"
36
37 #if defined(OS_CHROMEOS)
38 #include "chrome/browser/chromeos/login/user_manager.h"
39 #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
40 #include "chrome/browser/chromeos/settings/device_oauth2_token_service.h"
41 #include "chrome/browser/chromeos/settings/device_oauth2_token_service_factory.h"
42 #include "google_apis/gaia/gaia_constants.h"
43 #endif
44
45 namespace extensions {
46
47 namespace identity_constants {
48 const char kInvalidClientId[] = "Invalid OAuth2 Client ID.";
49 const char kInvalidScopes[] = "Invalid OAuth2 scopes.";
50 const char kAuthFailure[] = "OAuth2 request failed: ";
51 const char kNoGrant[] = "OAuth2 not granted or revoked.";
52 const char kUserRejected[] = "The user did not approve access.";
53 const char kUserNotSignedIn[] = "The user is not signed in.";
54 const char kInteractionRequired[] = "User interaction required.";
55 const char kInvalidRedirect[] = "Did not redirect to the right URL.";
56 const char kOffTheRecord[] = "Identity API is disabled in incognito windows.";
57 const char kPageLoadFailure[] = "Authorization page could not be loaded.";
58 const char kCanceled[] = "canceled";
59
60 const int kCachedIssueAdviceTTLSeconds = 1;
61 }  // namespace identity_constants
62
63 namespace {
64
65 static const char kChromiumDomainRedirectUrlPattern[] =
66     "https://%s.chromiumapp.org/";
67
68 std::string GetPrimaryAccountId(content::BrowserContext* context) {
69   SigninManagerBase* signin_manager =
70       SigninManagerFactory::GetForProfile(Profile::FromBrowserContext(context));
71   return signin_manager->GetAuthenticatedAccountId();
72 }
73
74 }  // namespace
75
76 namespace identity = api::identity;
77
78 IdentityTokenCacheValue::IdentityTokenCacheValue()
79     : status_(CACHE_STATUS_NOTFOUND) {}
80
81 IdentityTokenCacheValue::IdentityTokenCacheValue(
82     const IssueAdviceInfo& issue_advice)
83     : status_(CACHE_STATUS_ADVICE), issue_advice_(issue_advice) {
84   expiration_time_ =
85       base::Time::Now() + base::TimeDelta::FromSeconds(
86                               identity_constants::kCachedIssueAdviceTTLSeconds);
87 }
88
89 IdentityTokenCacheValue::IdentityTokenCacheValue(const std::string& token,
90                                                  base::TimeDelta time_to_live)
91     : status_(CACHE_STATUS_TOKEN), token_(token) {
92   // Remove 20 minutes from the ttl so cached tokens will have some time
93   // to live any time they are returned.
94   time_to_live -= base::TimeDelta::FromMinutes(20);
95
96   base::TimeDelta zero_delta;
97   if (time_to_live < zero_delta)
98     time_to_live = zero_delta;
99
100   expiration_time_ = base::Time::Now() + time_to_live;
101 }
102
103 IdentityTokenCacheValue::~IdentityTokenCacheValue() {}
104
105 IdentityTokenCacheValue::CacheValueStatus IdentityTokenCacheValue::status()
106     const {
107   if (is_expired())
108     return IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND;
109   else
110     return status_;
111 }
112
113 const IssueAdviceInfo& IdentityTokenCacheValue::issue_advice() const {
114   return issue_advice_;
115 }
116
117 const std::string& IdentityTokenCacheValue::token() const { return token_; }
118
119 bool IdentityTokenCacheValue::is_expired() const {
120   return status_ == CACHE_STATUS_NOTFOUND ||
121          expiration_time_ < base::Time::Now();
122 }
123
124 const base::Time& IdentityTokenCacheValue::expiration_time() const {
125   return expiration_time_;
126 }
127
128 IdentityAPI::IdentityAPI(content::BrowserContext* context)
129     : browser_context_(context),
130       account_tracker_(Profile::FromBrowserContext(context)) {
131   account_tracker_.AddObserver(this);
132 }
133
134 IdentityAPI::~IdentityAPI() {}
135
136 IdentityMintRequestQueue* IdentityAPI::mint_queue() { return &mint_queue_; }
137
138 void IdentityAPI::SetCachedToken(const ExtensionTokenKey& key,
139                                  const IdentityTokenCacheValue& token_data) {
140   CachedTokens::iterator it = token_cache_.find(key);
141   if (it != token_cache_.end() && it->second.status() <= token_data.status())
142     token_cache_.erase(it);
143
144   token_cache_.insert(std::make_pair(key, token_data));
145 }
146
147 void IdentityAPI::EraseCachedToken(const std::string& extension_id,
148                                    const std::string& token) {
149   CachedTokens::iterator it;
150   for (it = token_cache_.begin(); it != token_cache_.end(); ++it) {
151     if (it->first.extension_id == extension_id &&
152         it->second.status() == IdentityTokenCacheValue::CACHE_STATUS_TOKEN &&
153         it->second.token() == token) {
154       token_cache_.erase(it);
155       break;
156     }
157   }
158 }
159
160 void IdentityAPI::EraseAllCachedTokens() { token_cache_.clear(); }
161
162 const IdentityTokenCacheValue& IdentityAPI::GetCachedToken(
163     const ExtensionTokenKey& key) {
164   return token_cache_[key];
165 }
166
167 const IdentityAPI::CachedTokens& IdentityAPI::GetAllCachedTokens() {
168   return token_cache_;
169 }
170
171 void IdentityAPI::ReportAuthError(const GoogleServiceAuthError& error) {
172   account_tracker_.ReportAuthError(GetPrimaryAccountId(browser_context_),
173                                    error);
174 }
175
176 GoogleServiceAuthError IdentityAPI::GetAuthStatusForTest() const {
177   return account_tracker_.GetAuthStatus();
178 }
179
180 void IdentityAPI::Shutdown() {
181   FOR_EACH_OBSERVER(ShutdownObserver, shutdown_observer_list_, OnShutdown());
182   account_tracker_.RemoveObserver(this);
183   account_tracker_.Shutdown();
184 }
185
186 static base::LazyInstance<BrowserContextKeyedAPIFactory<IdentityAPI> >
187     g_factory = LAZY_INSTANCE_INITIALIZER;
188
189 // static
190 BrowserContextKeyedAPIFactory<IdentityAPI>* IdentityAPI::GetFactoryInstance() {
191   return g_factory.Pointer();
192 }
193
194 void IdentityAPI::OnAccountAdded(const AccountIds& ids) {}
195
196 void IdentityAPI::OnAccountRemoved(const AccountIds& ids) {}
197
198 void IdentityAPI::OnAccountSignInChanged(const AccountIds& ids,
199                                          bool is_signed_in) {
200   api::identity::AccountInfo account_info;
201   account_info.id = ids.gaia;
202
203   scoped_ptr<base::ListValue> args =
204       api::identity::OnSignInChanged::Create(account_info, is_signed_in);
205   scoped_ptr<Event> event(new Event(api::identity::OnSignInChanged::kEventName,
206                                     args.Pass(),
207                                     browser_context_));
208
209   EventRouter::Get(browser_context_)->BroadcastEvent(event.Pass());
210 }
211
212 void IdentityAPI::AddShutdownObserver(ShutdownObserver* observer) {
213   shutdown_observer_list_.AddObserver(observer);
214 }
215
216 void IdentityAPI::RemoveShutdownObserver(ShutdownObserver* observer) {
217   shutdown_observer_list_.RemoveObserver(observer);
218 }
219
220 template <>
221 void BrowserContextKeyedAPIFactory<IdentityAPI>::DeclareFactoryDependencies() {
222   DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
223   DependsOn(ProfileOAuth2TokenServiceFactory::GetInstance());
224 }
225
226 IdentityGetAuthTokenFunction::IdentityGetAuthTokenFunction()
227     : OAuth2TokenService::Consumer("extensions_identity_api"),
228       should_prompt_for_scopes_(false),
229       should_prompt_for_signin_(false) {}
230
231 IdentityGetAuthTokenFunction::~IdentityGetAuthTokenFunction() {}
232
233 bool IdentityGetAuthTokenFunction::RunAsync() {
234   if (GetProfile()->IsOffTheRecord()) {
235     error_ = identity_constants::kOffTheRecord;
236     return false;
237   }
238
239   scoped_ptr<identity::GetAuthToken::Params> params(
240       identity::GetAuthToken::Params::Create(*args_));
241   EXTENSION_FUNCTION_VALIDATE(params.get());
242   bool interactive = params->details.get() &&
243       params->details->interactive.get() &&
244       *params->details->interactive;
245
246   should_prompt_for_scopes_ = interactive;
247   should_prompt_for_signin_ = interactive;
248
249   const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
250
251   // Check that the necessary information is present in the manifest.
252   oauth2_client_id_ = GetOAuth2ClientId();
253   if (oauth2_client_id_.empty()) {
254     error_ = identity_constants::kInvalidClientId;
255     return false;
256   }
257
258   if (oauth2_info.scopes.size() == 0) {
259     error_ = identity_constants::kInvalidScopes;
260     return false;
261   }
262
263   std::set<std::string> scopes(oauth2_info.scopes.begin(),
264                                oauth2_info.scopes.end());
265   token_key_.reset(new ExtensionTokenKey(
266       GetExtension()->id(), GetPrimaryAccountId(GetProfile()), scopes));
267
268   // From here on out, results must be returned asynchronously.
269   StartAsyncRun();
270
271 #if defined(OS_CHROMEOS)
272   policy::BrowserPolicyConnectorChromeOS* connector =
273       g_browser_process->platform_part()->browser_policy_connector_chromeos();
274   if (chromeos::UserManager::Get()->IsLoggedInAsKioskApp() &&
275       connector->IsEnterpriseManaged()) {
276     StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE);
277     return true;
278   }
279 #endif
280
281   if (!HasLoginToken()) {
282     if (!should_prompt_for_signin_) {
283       CompleteFunctionWithError(identity_constants::kUserNotSignedIn);
284       return true;
285     }
286     // Display a login prompt.
287     StartSigninFlow();
288   } else {
289     StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE);
290   }
291
292   return true;
293 }
294
295 void IdentityGetAuthTokenFunction::StartAsyncRun() {
296   // Balanced in CompleteAsyncRun
297   AddRef();
298   extensions::IdentityAPI::GetFactoryInstance()
299       ->Get(GetProfile())
300       ->AddShutdownObserver(this);
301 }
302
303 void IdentityGetAuthTokenFunction::CompleteAsyncRun(bool success) {
304   extensions::IdentityAPI::GetFactoryInstance()
305       ->Get(GetProfile())
306       ->RemoveShutdownObserver(this);
307
308   SendResponse(success);
309   Release();  // Balanced in StartAsyncRun
310 }
311
312 void IdentityGetAuthTokenFunction::CompleteFunctionWithResult(
313     const std::string& access_token) {
314
315   SetResult(new base::StringValue(access_token));
316   CompleteAsyncRun(true);
317 }
318
319 void IdentityGetAuthTokenFunction::CompleteFunctionWithError(
320     const std::string& error) {
321   error_ = error;
322   CompleteAsyncRun(false);
323 }
324
325 void IdentityGetAuthTokenFunction::StartSigninFlow() {
326   // All cached tokens are invalid because the user is not signed in.
327   IdentityAPI* id_api =
328       extensions::IdentityAPI::GetFactoryInstance()->Get(GetProfile());
329   id_api->EraseAllCachedTokens();
330   // Display a login prompt. If the subsequent mint fails, don't display the
331   // login prompt again.
332   should_prompt_for_signin_ = false;
333   ShowLoginPopup();
334 }
335
336 void IdentityGetAuthTokenFunction::StartMintTokenFlow(
337     IdentityMintRequestQueue::MintType type) {
338   mint_token_flow_type_ = type;
339
340   // Flows are serialized to prevent excessive traffic to GAIA, and
341   // to consolidate UI pop-ups.
342   IdentityAPI* id_api =
343       extensions::IdentityAPI::GetFactoryInstance()->Get(GetProfile());
344
345   if (!should_prompt_for_scopes_) {
346     // Caller requested no interaction.
347
348     if (type == IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE) {
349       // GAIA told us to do a consent UI.
350       CompleteFunctionWithError(identity_constants::kNoGrant);
351       return;
352     }
353     if (!id_api->mint_queue()->empty(
354             IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE, *token_key_)) {
355       // Another call is going through a consent UI.
356       CompleteFunctionWithError(identity_constants::kNoGrant);
357       return;
358     }
359   }
360   id_api->mint_queue()->RequestStart(type, *token_key_, this);
361 }
362
363 void IdentityGetAuthTokenFunction::CompleteMintTokenFlow() {
364   IdentityMintRequestQueue::MintType type = mint_token_flow_type_;
365
366   const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
367   std::set<std::string> scopes(oauth2_info.scopes.begin(),
368                                oauth2_info.scopes.end());
369
370   extensions::IdentityAPI::GetFactoryInstance()
371       ->Get(GetProfile())
372       ->mint_queue()
373       ->RequestComplete(type, *token_key_, this);
374 }
375
376 void IdentityGetAuthTokenFunction::StartMintToken(
377     IdentityMintRequestQueue::MintType type) {
378   const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
379   IdentityAPI* id_api = IdentityAPI::GetFactoryInstance()->Get(GetProfile());
380   IdentityTokenCacheValue cache_entry = id_api->GetCachedToken(*token_key_);
381   IdentityTokenCacheValue::CacheValueStatus cache_status =
382       cache_entry.status();
383
384   if (type == IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE) {
385     switch (cache_status) {
386       case IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND:
387 #if defined(OS_CHROMEOS)
388         // Always force minting token for ChromeOS kiosk app.
389         if (chromeos::UserManager::Get()->IsLoggedInAsKioskApp()) {
390           gaia_mint_token_mode_ = OAuth2MintTokenFlow::MODE_MINT_TOKEN_FORCE;
391           policy::BrowserPolicyConnectorChromeOS* connector =
392               g_browser_process->platform_part()
393                   ->browser_policy_connector_chromeos();
394           if (connector->IsEnterpriseManaged()) {
395             StartDeviceLoginAccessTokenRequest();
396           } else {
397             StartLoginAccessTokenRequest();
398           }
399           return;
400         }
401 #endif
402
403         if (oauth2_info.auto_approve)
404           // oauth2_info.auto_approve is protected by a whitelist in
405           // _manifest_features.json hence only selected extensions take
406           // advantage of forcefully minting the token.
407           gaia_mint_token_mode_ = OAuth2MintTokenFlow::MODE_MINT_TOKEN_FORCE;
408         else
409           gaia_mint_token_mode_ = OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE;
410         StartLoginAccessTokenRequest();
411         break;
412
413       case IdentityTokenCacheValue::CACHE_STATUS_TOKEN:
414         CompleteMintTokenFlow();
415         CompleteFunctionWithResult(cache_entry.token());
416         break;
417
418       case IdentityTokenCacheValue::CACHE_STATUS_ADVICE:
419         CompleteMintTokenFlow();
420         should_prompt_for_signin_ = false;
421         issue_advice_ = cache_entry.issue_advice();
422         StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE);
423         break;
424     }
425   } else {
426     DCHECK(type == IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE);
427
428     if (cache_status == IdentityTokenCacheValue::CACHE_STATUS_TOKEN) {
429       CompleteMintTokenFlow();
430       CompleteFunctionWithResult(cache_entry.token());
431     } else {
432       ShowOAuthApprovalDialog(issue_advice_);
433     }
434   }
435 }
436
437 void IdentityGetAuthTokenFunction::OnMintTokenSuccess(
438     const std::string& access_token, int time_to_live) {
439   IdentityTokenCacheValue token(access_token,
440                                 base::TimeDelta::FromSeconds(time_to_live));
441   IdentityAPI::GetFactoryInstance()->Get(GetProfile())->SetCachedToken(
442       *token_key_, token);
443
444   CompleteMintTokenFlow();
445   CompleteFunctionWithResult(access_token);
446 }
447
448 void IdentityGetAuthTokenFunction::OnMintTokenFailure(
449     const GoogleServiceAuthError& error) {
450   CompleteMintTokenFlow();
451
452   switch (error.state()) {
453     case GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS:
454     case GoogleServiceAuthError::ACCOUNT_DELETED:
455     case GoogleServiceAuthError::ACCOUNT_DISABLED:
456       extensions::IdentityAPI::GetFactoryInstance()
457           ->Get(GetProfile())
458           ->ReportAuthError(error);
459       if (should_prompt_for_signin_) {
460         // Display a login prompt and try again (once).
461         StartSigninFlow();
462         return;
463       }
464       break;
465     default:
466       // Return error to caller.
467       break;
468   }
469
470   CompleteFunctionWithError(
471       std::string(identity_constants::kAuthFailure) + error.ToString());
472 }
473
474 void IdentityGetAuthTokenFunction::OnIssueAdviceSuccess(
475     const IssueAdviceInfo& issue_advice) {
476   IdentityAPI::GetFactoryInstance()->Get(GetProfile())->SetCachedToken(
477       *token_key_, IdentityTokenCacheValue(issue_advice));
478   CompleteMintTokenFlow();
479
480   should_prompt_for_signin_ = false;
481   // Existing grant was revoked and we used NO_FORCE, so we got info back
482   // instead. Start a consent UI if we can.
483   issue_advice_ = issue_advice;
484   StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE);
485 }
486
487 void IdentityGetAuthTokenFunction::SigninSuccess() {
488   StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE);
489 }
490
491 void IdentityGetAuthTokenFunction::SigninFailed() {
492   CompleteFunctionWithError(identity_constants::kUserNotSignedIn);
493 }
494
495 void IdentityGetAuthTokenFunction::OnGaiaFlowFailure(
496     GaiaWebAuthFlow::Failure failure,
497     GoogleServiceAuthError service_error,
498     const std::string& oauth_error) {
499   CompleteMintTokenFlow();
500   std::string error;
501
502   switch (failure) {
503     case GaiaWebAuthFlow::WINDOW_CLOSED:
504       error = identity_constants::kUserRejected;
505       break;
506
507     case GaiaWebAuthFlow::INVALID_REDIRECT:
508       error = identity_constants::kInvalidRedirect;
509       break;
510
511     case GaiaWebAuthFlow::SERVICE_AUTH_ERROR:
512       error = std::string(identity_constants::kAuthFailure) +
513           service_error.ToString();
514       break;
515
516     case GaiaWebAuthFlow::OAUTH_ERROR:
517       error = MapOAuth2ErrorToDescription(oauth_error);
518       break;
519
520     case GaiaWebAuthFlow::LOAD_FAILED:
521       error = identity_constants::kPageLoadFailure;
522       break;
523
524     default:
525       NOTREACHED() << "Unexpected error from gaia web auth flow: " << failure;
526       error = identity_constants::kInvalidRedirect;
527       break;
528   }
529
530   CompleteFunctionWithError(error);
531 }
532
533 void IdentityGetAuthTokenFunction::OnGaiaFlowCompleted(
534     const std::string& access_token,
535     const std::string& expiration) {
536
537   int time_to_live;
538   if (!expiration.empty() && base::StringToInt(expiration, &time_to_live)) {
539     IdentityTokenCacheValue token_value(
540         access_token, base::TimeDelta::FromSeconds(time_to_live));
541     IdentityAPI::GetFactoryInstance()->Get(GetProfile())->SetCachedToken(
542         *token_key_, token_value);
543   }
544
545   CompleteMintTokenFlow();
546   CompleteFunctionWithResult(access_token);
547 }
548
549 void IdentityGetAuthTokenFunction::OnGetTokenSuccess(
550     const OAuth2TokenService::Request* request,
551     const std::string& access_token,
552     const base::Time& expiration_time) {
553   login_token_request_.reset();
554   StartGaiaRequest(access_token);
555 }
556
557 void IdentityGetAuthTokenFunction::OnGetTokenFailure(
558     const OAuth2TokenService::Request* request,
559     const GoogleServiceAuthError& error) {
560   login_token_request_.reset();
561   OnGaiaFlowFailure(GaiaWebAuthFlow::SERVICE_AUTH_ERROR, error, std::string());
562 }
563
564 void IdentityGetAuthTokenFunction::OnShutdown() {
565   gaia_web_auth_flow_.reset();
566   signin_flow_.reset();
567   login_token_request_.reset();
568   extensions::IdentityAPI::GetFactoryInstance()
569       ->Get(GetProfile())
570       ->mint_queue()
571       ->RequestCancel(*token_key_, this);
572   CompleteFunctionWithError(identity_constants::kCanceled);
573 }
574
575 #if defined(OS_CHROMEOS)
576 void IdentityGetAuthTokenFunction::StartDeviceLoginAccessTokenRequest() {
577   chromeos::DeviceOAuth2TokenService* service =
578       chromeos::DeviceOAuth2TokenServiceFactory::Get();
579   // Since robot account refresh tokens are scoped down to [any-api] only,
580   // request access token for [any-api] instead of login.
581   OAuth2TokenService::ScopeSet scopes;
582   scopes.insert(GaiaConstants::kAnyApiOAuth2Scope);
583   login_token_request_ =
584       service->StartRequest(service->GetRobotAccountId(),
585                             scopes,
586                             this);
587 }
588 #endif
589
590 void IdentityGetAuthTokenFunction::StartLoginAccessTokenRequest() {
591   ProfileOAuth2TokenService* service =
592       ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile());
593   const std::string primary_account_id = GetPrimaryAccountId(GetProfile());
594 #if defined(OS_CHROMEOS)
595   if (chrome::IsRunningInForcedAppMode()) {
596     std::string app_client_id;
597     std::string app_client_secret;
598     if (chromeos::UserManager::Get()->GetAppModeChromeClientOAuthInfo(
599            &app_client_id, &app_client_secret)) {
600       login_token_request_ =
601           service->StartRequestForClient(primary_account_id,
602                                          app_client_id,
603                                          app_client_secret,
604                                          OAuth2TokenService::ScopeSet(),
605                                          this);
606       return;
607     }
608   }
609 #endif
610   login_token_request_ = service->StartRequest(
611       primary_account_id, OAuth2TokenService::ScopeSet(), this);
612 }
613
614 void IdentityGetAuthTokenFunction::StartGaiaRequest(
615     const std::string& login_access_token) {
616   DCHECK(!login_access_token.empty());
617   mint_token_flow_.reset(CreateMintTokenFlow(login_access_token));
618   mint_token_flow_->Start();
619 }
620
621 void IdentityGetAuthTokenFunction::ShowLoginPopup() {
622   signin_flow_.reset(new IdentitySigninFlow(this, GetProfile()));
623   signin_flow_->Start();
624 }
625
626 void IdentityGetAuthTokenFunction::ShowOAuthApprovalDialog(
627     const IssueAdviceInfo& issue_advice) {
628   const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
629   const std::string locale = g_browser_process->local_state()->GetString(
630       prefs::kApplicationLocale);
631
632   gaia_web_auth_flow_.reset(new GaiaWebAuthFlow(
633       this, GetProfile(), GetExtension()->id(), oauth2_info, locale));
634   gaia_web_auth_flow_->Start();
635 }
636
637 OAuth2MintTokenFlow* IdentityGetAuthTokenFunction::CreateMintTokenFlow(
638     const std::string& login_access_token) {
639   const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
640
641   OAuth2MintTokenFlow* mint_token_flow = new OAuth2MintTokenFlow(
642       GetProfile()->GetRequestContext(),
643       this,
644       OAuth2MintTokenFlow::Parameters(login_access_token,
645                                       GetExtension()->id(),
646                                       oauth2_client_id_,
647                                       oauth2_info.scopes,
648                                       gaia_mint_token_mode_));
649   return mint_token_flow;
650 }
651
652 bool IdentityGetAuthTokenFunction::HasLoginToken() const {
653   ProfileOAuth2TokenService* token_service =
654       ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile());
655   return token_service->RefreshTokenIsAvailable(
656       GetPrimaryAccountId(GetProfile()));
657 }
658
659 std::string IdentityGetAuthTokenFunction::MapOAuth2ErrorToDescription(
660     const std::string& error) {
661   const char kOAuth2ErrorAccessDenied[] = "access_denied";
662   const char kOAuth2ErrorInvalidScope[] = "invalid_scope";
663
664   if (error == kOAuth2ErrorAccessDenied)
665     return std::string(identity_constants::kUserRejected);
666   else if (error == kOAuth2ErrorInvalidScope)
667     return std::string(identity_constants::kInvalidScopes);
668   else
669     return std::string(identity_constants::kAuthFailure) + error;
670 }
671
672 std::string IdentityGetAuthTokenFunction::GetOAuth2ClientId() const {
673   const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
674   std::string client_id = oauth2_info.client_id;
675
676   // Component apps using auto_approve may use Chrome's client ID by
677   // omitting the field.
678   if (client_id.empty() && GetExtension()->location() == Manifest::COMPONENT &&
679       oauth2_info.auto_approve) {
680     client_id = GaiaUrls::GetInstance()->oauth2_chrome_client_id();
681   }
682   return client_id;
683 }
684
685 IdentityRemoveCachedAuthTokenFunction::IdentityRemoveCachedAuthTokenFunction() {
686 }
687
688 IdentityRemoveCachedAuthTokenFunction::
689     ~IdentityRemoveCachedAuthTokenFunction() {
690 }
691
692 bool IdentityRemoveCachedAuthTokenFunction::RunSync() {
693   if (GetProfile()->IsOffTheRecord()) {
694     error_ = identity_constants::kOffTheRecord;
695     return false;
696   }
697
698   scoped_ptr<identity::RemoveCachedAuthToken::Params> params(
699       identity::RemoveCachedAuthToken::Params::Create(*args_));
700   EXTENSION_FUNCTION_VALIDATE(params.get());
701   IdentityAPI::GetFactoryInstance()->Get(GetProfile())->EraseCachedToken(
702       GetExtension()->id(), params->details.token);
703   return true;
704 }
705
706 IdentityLaunchWebAuthFlowFunction::IdentityLaunchWebAuthFlowFunction() {}
707
708 IdentityLaunchWebAuthFlowFunction::~IdentityLaunchWebAuthFlowFunction() {
709   if (auth_flow_)
710     auth_flow_.release()->DetachDelegateAndDelete();
711 }
712
713 bool IdentityLaunchWebAuthFlowFunction::RunAsync() {
714   if (GetProfile()->IsOffTheRecord()) {
715     error_ = identity_constants::kOffTheRecord;
716     return false;
717   }
718
719   scoped_ptr<identity::LaunchWebAuthFlow::Params> params(
720       identity::LaunchWebAuthFlow::Params::Create(*args_));
721   EXTENSION_FUNCTION_VALIDATE(params.get());
722
723   GURL auth_url(params->details.url);
724   WebAuthFlow::Mode mode =
725       params->details.interactive && *params->details.interactive ?
726       WebAuthFlow::INTERACTIVE : WebAuthFlow::SILENT;
727
728   // Set up acceptable target URLs. (Does not include chrome-extension
729   // scheme for this version of the API.)
730   InitFinalRedirectURLPrefix(GetExtension()->id());
731
732   AddRef();  // Balanced in OnAuthFlowSuccess/Failure.
733
734   auth_flow_.reset(new WebAuthFlow(this, GetProfile(), auth_url, mode));
735   auth_flow_->Start();
736   return true;
737 }
738
739 void IdentityLaunchWebAuthFlowFunction::InitFinalRedirectURLPrefixForTest(
740     const std::string& extension_id) {
741   InitFinalRedirectURLPrefix(extension_id);
742 }
743
744 void IdentityLaunchWebAuthFlowFunction::InitFinalRedirectURLPrefix(
745     const std::string& extension_id) {
746   if (final_url_prefix_.is_empty()) {
747     final_url_prefix_ = GURL(base::StringPrintf(
748         kChromiumDomainRedirectUrlPattern, extension_id.c_str()));
749   }
750 }
751
752 void IdentityLaunchWebAuthFlowFunction::OnAuthFlowFailure(
753     WebAuthFlow::Failure failure) {
754   switch (failure) {
755     case WebAuthFlow::WINDOW_CLOSED:
756       error_ = identity_constants::kUserRejected;
757       break;
758     case WebAuthFlow::INTERACTION_REQUIRED:
759       error_ = identity_constants::kInteractionRequired;
760       break;
761     case WebAuthFlow::LOAD_FAILED:
762       error_ = identity_constants::kPageLoadFailure;
763       break;
764     default:
765       NOTREACHED() << "Unexpected error from web auth flow: " << failure;
766       error_ = identity_constants::kInvalidRedirect;
767       break;
768   }
769   SendResponse(false);
770   Release();  // Balanced in RunAsync.
771 }
772
773 void IdentityLaunchWebAuthFlowFunction::OnAuthFlowURLChange(
774     const GURL& redirect_url) {
775   if (redirect_url.GetWithEmptyPath() == final_url_prefix_) {
776     SetResult(new base::StringValue(redirect_url.spec()));
777     SendResponse(true);
778     Release();  // Balanced in RunAsync.
779   }
780 }
781
782 }  // namespace extensions