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